how to test and mock react function/component - reactjs

this is my test for user registration using jest + react testing library, the problem is that the test update the db.
therefore at the second run the test fails (beacuse the first run registered the user)
so my question is anyone know how can I mock this function?
I will be grateful for any help I could get. thanks in advance
the test
test('signup should dispatch signupAction', async () => {
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
initialState = {
authReducer: { isAuthenticatedData: false },
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<Router>
<UserSignup />
</Router>
</Provider>
);
const nameTextbox = screen.getByPlaceholderText('Name*');
const emailTextbox = screen.getByPlaceholderText('Email*');
const passwordTextbox = screen.getByPlaceholderText('Password*');
const confirmTextbox = screen.getByPlaceholderText('Confirm Password*');
const signupButton = screen.getByRole('button', { name: 'Register' });
userEvent.type(nameTextbox, 'newtestuser');
userEvent.type(emailTextbox, 'newtestuser#gmail.com');
userEvent.type(passwordTextbox, 'testuser123');
userEvent.type(confirmTextbox, 'testuser123');
userEvent.click(signupButton);
await waitFor(() => expect(store.getActions()[0].type).toBe('SIGNUP_SUCCESS'));
});
sign up component
const userSignup = () => {
const dispatch = useDispatch();
const isAuthenticatedData = useSelector((state) => state.authReducer.isAuthenticatedData);
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
re_password: '',
});
const [accountCreated, setAccountCreated] = useState(false);
const { name, email, password, re_password } = formData;
const onChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = (e) => {
e.preventDefault();
if (password === re_password) {
try {
dispatch(
signupAction({
name,
email,
password,
re_password,
})
);
setAccountCreated(true);
} catch {
window.scrollTo(0, 0);
}
}
};
if (isAuthenticatedData) return <Redirect to='/' />;
if (accountCreated) return <Redirect to='/login' />;
return (
<div data-testid='userSignup'>
<h1>Sign Up</h1>
<p>Create your Account</p>
<form onSubmit={(e) => onSubmit(e)}>
<div>
<input
type='text'
placeholder='Name*'
name='name'
value={name}
onChange={(e) => onChange(e)}
required
/>
</div>
<div>
<input
type='email'
placeholder='Email*'
name='email'
value={email}
onChange={(e) => onChange(e)}
required
/>
</div>
<div>
<input
type='password'
placeholder='Password*'
name='password'
value={password}
onChange={(e) => onChange(e)}
minLength='6'
required
/>
</div>
<div>
<input
type='password'
placeholder='Confirm Password*'
name='re_password'
value={re_password}
onChange={(e) => onChange(e)}
minLength='6'
required
/>
</div>
<button type='submit'>Register</button>
</form>
<p>
Already have an account? <Link to='/login'>Sign In</Link>
</p>
</div>
);
};
export default connect()(userSignup);
sign up action
export const signupAction =
({ name, email, password, re_password }) =>
async (dispatch) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({
name,
email,
password,
re_password,
});
try {
const res = await axios.post(`${process.env.REACT_APP_API_URL}/api/djoser/users/`, body, config);
dispatch({ type: SIGNUP_SUCCESS, payload: res.data });
} catch (err) {
dispatch({ type: SIGNUP_FAIL });
}
};

Assuming you are writing unit tests (which is probably where you should start), then you are looking for a concept called "mocking." The idea is that your React unit tests should only test your React code. Your React unit tests should not depend on a database or even an API. That introduces all sorts of challenges, as you have discovered.
Basically how mocking frameworks work is you configure them with some fake data. Then when you run the tests, your code uses that fake data instead of calling the API.
I see you are using axios to call your API. I suggest you check out axios-mock-adapter to help you mock those axios calls.

I managed to solve this problem here is the test for those who needs it
import '#testing-library/jest-dom/extend-expect';
import { render, screen } from '#testing-library/react';
import { Provider } from 'react-redux';
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import UserSignup from '../../../components/users/UserSignup';
import configureStore from 'redux-mock-store';
import { signupAction } from '../../../redux/actions/auth';
import thunk from 'redux-thunk';
import userEvent from '#testing-library/user-event';
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
let initialState = {
authReducer: {},
};
const store = mockStore(initialState);
jest.mock('../../../redux/actions/auth', () => ({ signupAction: jest.fn() }));
test('Redux - signup should dispatch signupAction', () => {
render(
<Provider store={store}>
<Router>
<UserSignup />
</Router>
</Provider>
);
initialState = {
authReducer: { isAuthenticatedData: false },
};
const store = mockStore(initialState);
render(
<Provider store={store}>
<Router>
<UserSignup />
</Router>
</Provider>
);
const nameTextbox = screen.getByPlaceholderText('Name*');
const emailTextbox = screen.getByPlaceholderText('Email*');
const passwordTextbox = screen.getByPlaceholderText('Password*');
const confirmTextbox = screen.getByPlaceholderText('Confirm Password*');
const signupButton = screen.getByRole('button', { name: 'Register' });
const nameValue = 'testuser';
const emailValue = 'testuser#gmail.com';
const passwordValue = 'testuser123';
const rePasswordValue = 'testuser123';
userEvent.type(nameTextbox, nameValue);
userEvent.type(emailTextbox, emailValue);
userEvent.type(passwordTextbox, passwordValue);
userEvent.type(confirmTextbox, rePasswordValue);
userEvent.click(signupButton);
const timesActionDispatched = signupAction.mock.calls.length;
expect(timesActionDispatched).toBe(1);
expect(signupAction.mock.calls[0][0].name).toEqual(nameValue);
expect(signupAction.mock.calls[0][0].email).toEqual(emailValue);
expect(signupAction.mock.calls[0][0].password).toEqual(passwordValue);
expect(signupAction.mock.calls[0][0].re_password).toEqual(rePasswordValue);
});

Related

Error(Uncaught TypeError): Cannot read properties of undefined (reading 'params')

i am using react-router-dom v6 this code generating above error message ..please help me to solve the error ... backend is working fine ..i think this error is coming from fronted .... it works with postman ..
i am following a older tutorial ... now i installed new version of react-router-dom ... please help me out
this is ResetPassword.js file
import React, { Fragment, useState, useEffect } from "react";
import "./ResetPassword.css";
import Loader from "../layout/Loader/Loader";
import { useDispatch, useSelector } from "react-redux";
import { clearErrors, resetPassword } from "../../actions/userAction";
import { useAlert } from "react-alert";
import MetaData from "../layout/MetaData";
import LockOpenIcon from "#material-ui/icons/LockOpen";
import LockIcon from "#material-ui/icons/Lock";
const ResetPassword = ({ history, match }) => {
const dispatch = useDispatch();
const alert = useAlert();
const { error, success, loading } = useSelector(
(state) => state.forgotPassword
);
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const resetPasswordSubmit = (e) => {
e.preventDefault();
const myForm = new FormData();
myForm.set("password", password);
myForm.set("confirmPassword", confirmPassword);
dispatch(resetPassword(match.params.token, myForm));
};
useEffect(() => {
if (error) {
alert.error(error);
dispatch(clearErrors());
}
if (success) {
alert.success("Password Updated Successfully");
history.push("/login");
}
}, [dispatch, error, alert, history, success]);
return (
<Fragment>
{loading ? (
<Loader />
) : (
<Fragment>
<MetaData title="Change Password" />
<div className="resetPasswordContainer">
<div className="resetPasswordBox">
<h2 className="resetPasswordHeading">Update Profile</h2>
<form
className="resetPasswordForm"
onSubmit={resetPasswordSubmit}
>
<div>
<LockOpenIcon />
<input
type="password"
placeholder="New Password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="loginPassword">
<LockIcon />
<input
type="password"
placeholder="Confirm Password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
</div>
<input
type="submit"
value="Update"
className="resetPasswordBtn"
/>
</form>
</div>
</div>
</Fragment>
)}
</Fragment>
);
};
export default ResetPassword;
And The backend code is here
export const resetPassword = (token, passwords) => async (dispatch) => {
try {
dispatch({ type: RESET_PASSWORD_REQUEST });
const config = { headers: { "Content-Type": "application/json" } };
const { data } = await axios.put(
`/api/v1/password/reset/${token}`,
passwords,
config
);
dispatch({ type: RESET_PASSWORD_SUCCESS, payload: data.success });
} catch (error) {
dispatch({
type: RESET_PASSWORD_FAIL,
payload: error.response.data.message,
});
}
};
Thank you
In react-router-dom#6 the Route component API changed significantly. There are no longer any route props (i.e. no match or history props) all replaced by React hooks. The history object was replaced by a navigate function via the useNavigate hook, and route path params are accessible via the useParams hook.
Example:
import { useNavigate, useParams } from 'react-router-dom';
const ResetPassword = () => {
const navigate = useNavigate(); // <-- access navigate function
const { token } = useParams(); // <-- access token path parameter
...
const resetPasswordSubmit = (e) => {
...
dispatch(resetPassword(token, myForm)); // <-- use token param here
};
useEffect(() => {
...
if (success) {
alert.success("Password Updated Successfully");
navigate("/login"); // <-- call navigate here
}
}, [dispatch, error, alert, navigate, success]);

How to pass functions and variables from Context API Provider to another components?

I need to restrict links in react router by specific user roles (I have roles stored in token). What I'm trying to do now is:
send username & password through SignIn component to getTokens() function from custom useAuth hook on submit to then pass a boolean isModerator inside route value to ensure that the user have the required authorities for the link to show. In my case request is just not going to the server on form submit, probably because I misuse context api or react itself somehow.
So this is how my useAuth hook looks right now:
import React, { useState, createContext, useContext, useEffect } from "react";
import axios from "axios";
export const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [authed, setAuthed] = useState(false);
const [moderator, setModerator] = useState(false);
const [accessToken, setAccessToken] = useState("");
const [refreshToken, setRefreshToken] = useState("");
const [authorities, setAuthorities] = useState([]);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const signIn = async (e, username, password) => {
e.preventDefault();
const result = await getTokens(username, password);
if (result) {
console.log("User has signed in");
setAuthed(true);
}
};
const isModerator = async () => {
const result = await getAccessTokenAuthorities();
if (result) {
console.log("User is admin");
setModerator(true);
}
};
const getTokens = async (username, password) => {
const api = `http://localhost:8080/api/v1/public/signIn?username=${username}&password=${password}`;
const res = await axios.get(api, {
withCredentials: true,
params: {
username: username,
password: password,
},
});
const data = await res.data;
setAccessToken(data["access_token"]);
setRefreshToken(data["refresh_token"]);
console.log(data);
return accessToken, refreshToken;
};
const getAccessTokenAuthorities = async () => {
const api = `http://localhost:8080/api/v1/public/getAccessTokenAuthorities`;
const res = await axios.get(api, {
withCredentials: true,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const data = await res.data;
setAuthorities(data);
let vals = [];
authorities.forEach((authority) => {
vals.push(Object.values(authority));
});
const check = vals.filter((val) => val.toString() === "MODERATOR");
if (check.length > 0) return !isModerator;
console.log(authorities);
return isModerator;
};
return (
<AuthContext.Provider
value={{
authed,
setAuthed,
moderator,
setModerator,
getTokens,
getAccessTokenAuthorities,
username,
password,
setUsername,
setPassword,
}}
>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
And this is me trying to use AuthContext in SignIn component:
import React, { useContext, useEffect, useState } from "react";
import { useAuth } from "../hooks/useAuth";
import { AuthContext } from "../hooks/useAuth";
const SignIn = (props) => {
const auth = useAuth();
const userDetails = useContext(AuthContext);
return (
<>
<h1>Вход</h1>
<form
method="get"
onSubmit={(e) => auth.signIn(e)}
encType="application/json"
>
<label htmlFor="username">Имя пользователя</label>
<input
type="text"
id="username"
onChange={(e) => userDetails.setUsername(e.target.value)}
></input>
<label htmlFor="password">Пароль</label>
<input
type="password"
id="password"
onChange={(e) => userDetails.setPassword(e.target.value)}
></input>
Вход
<input type="submit"></input>
</form>
</>
);
};
SignIn.propTypes = {};
export default SignIn;
Here is how I set my AuthProvider in index.js:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css"; import reportWebVitals from "./reportWebVitals";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Inventory from "./components/Inventory";
import SignIn from "./components/SignIn";
import { AuthProvider } from "./hooks/useAuth";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
<Route path="/"
element={<App />}>
</Route>
<Route path="api/v1/public/signIn"
element={<SignIn />}>
</Route>
<Route path="api/v1/moderator/inventory" element={<Inventory />} >
</Route>
</Routes>
</AuthProvider>
</BrowserRouter>
</React.StrictMode> );
reportWebVitals();
Thanks in advance.
You're creating 2 instances of context when you initialize 2 variables of it.
Both of these variables are behaving as separate instances of context. Its like when an object is initialized from a constructor.
All the methods you've passed to context Provider are available on const auth=useAuth(). Inside your Signin component, you're calling userDetails.setUsername() for changing value of username and to submit the form you're calling auth.signin.
You can simply use auth.setUsername(e.target.value) and auth.setPassword(e.target.value). For submitting form use auth.signin()
const SignIn = (props) => {
const auth = useAuth();
// const userDetails = useContext(AuthContext); No need to initialize this one
useEffect(() => {
// here in this log you will see all the methods are available from context provider
if (auth) console.log(auth);
}, [auth]);
return (
<>
<h1>Вход</h1>
<form
method="get"
onSubmit={(e) => auth.signIn(e)}
encType="application/json"
>
<label htmlFor="username">Имя пользователя</label>
<input
type="text"
id="username"
onChange={(e) => auth.setUsername(e.target.value)}
></input>
<label htmlFor="password">Пароль</label>
<input
type="password"
id="password"
onChange={(e) => auth.setPassword(e.target.value)}
></input>
Вход
<input type="submit"></input>
</form>
</>
);
};
export default SignIn;

Got Received number of calls: 0 error for login functionality

I am writing unit test case for login.
I am unsure about how to test handle submit as it contains one of the service call in the form of getToken() method, it would be greate if someone can guide me through how to handle this situation.
export const getToken = (credentials) => {
const token = 'abccss';
if (
credentials.username === 'test#test.com' &&
credentials.password === '123'
) {
return token;
} else {
return null;
}
};
The above code fetches user name and password and sends it to login in handleSubmit() function
//all imports(loginservice,auth etc etc)
import './Login.scss';
const Login = () => {
const [email, setEmail] = useState('');
const [pwd, setPwd] = useState('');
const authCon = useContext(AuthContext);
const handleSubmit = (e) => {
e.preventDefault();
const token = getToken({ username: email, password: pwd });
if (token) {
authCon.login(token);
window.location.href = '/dashboard';
}
};
return (
<div className="div-login">
<div className="div-login-logo">
<img src={logo} alt="Logo"></img>
</div>
<div>
<form onSubmit={handleSubmit}>
<input
className="credentials-input"
type="email"
value={email}
placeholder="Email Address"
required
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="credentials-input"
type="password"
value={pwd}
placeholder="Password"
required
onChange={(e) => setPwd(e.target.value)}
/>
<button className="login-button" type="submit">
Log In
</button>
</form>
</div>
</div>
);
};
export default Login;
Test Code
test('Submit shoud work successfully', () => {
const mockLogin = jest.fn();
const { getByRole } = render(<Login handleSubmit={mockLogin} />);
const login_button = getByRole('button');
fireEvent.submit(login_button);
expect(mockLogin).toHaveBeenCalledTimes(1);
});
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
As I am new to React, help will be appreciated.
The actual issue is handleSubmit is not a props of Login component.
Also you can't test the internal methods of a component using React testing Library, you have to move the handleSubmit method to either parent component or a common file and pass it to the login component or import it so that you can mock the method and perform the test.
Move the getToken and handleSubmit to a common file like below,
common.ts
export const getToken = (credentials:any) => {
const token = 'abccss';
if (
credentials.username === 'test#test.com' &&
credentials.password === '123'
) {
return token;
} else {
return null;
}
};
export const handleSubmit = (e:any, email:string, pwd: string) => {
e.preventDefault();
const token = getToken({ username: email, password: pwd });
if (token) {
// authCon.login(token);
window.location.href = '/dashboard';
}
};
Modify Login.ts as like below ( see below handleSubmit is not internal and its imported from common.ts file so we that we can mock it)
import React, { useContext, useState } from 'react';
import { getToken, handleSubmit } from './common';
const Login = () => {
const [email, setEmail] = useState('');
const [pwd, setPwd] = useState('');
// const authCon = useContext(AuthContext);
return (
<div className="div-login">
<div className="div-login-logo">
{/* <img src={logo} alt="Logo"></img> */}
</div>
<div>
<form onSubmit={(e) => handleSubmit(e, email, pwd)}>
<input
className="credentials-input"
type="email"
value={email}
placeholder="Email Address"
required
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="credentials-input"
type="password"
value={pwd}
placeholder="Password"
required
onChange={(e) => setPwd(e.target.value)}
/>
<button className="login-button" type="submit">
Log In
</button>
</form>
</div>
</div>
);
};
export default Login;
And finally Login.test.tsx shown below
import { fireEvent, render, screen } from '#testing-library/react';
import Login from './Login';
import * as CommonModule from './common';
jest.mock('./common');
test('Submit shoud work successfully', () => {
const mockLogin = jest.spyOn(CommonModule,'handleSubmit').mockImplementation();
const { getByRole } = render(<Login />);
const login_button = getByRole('button');
fireEvent.submit(login_button);
expect(mockLogin).toHaveBeenCalledTimes(1);
});
Test Result :

managing state in next.js with redux

I am new to next.js and I am building a small application to test how to manage state which I will implement in a larger app. I followed the documentation to set up my store... below is my reducer
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const combinedReducer = combineReducers({
reducers,
})
const reducer = (state, action) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
}
if (state.loading.loading) nextState.loading.loading = state.loading.loading // preserve count value on client side navigation
return nextState
} else {
return combinedReducer(state, action)
}
}
const initStore = () => {
return createStore(reducer, bindMiddleware([thunkMiddleware]))
}
export const wrapper = createWrapper(initStore)
and my -app.js folder
import { wrapper } from '../Store/store'
const WrappedApp = ({ Component, pageProps }) => {
return (
<Fragment>
<HeadComponent />
<Component {...pageProps} />
</Fragment>
)
}
export default wrapper.withRedux(WrappedApp)
in my index.js file which I will also share, I managed the state locally which is fine since it is a small app. I want to learn how to manage this with redux so that I can replicate this in a larger project I will be working on. thanks
const App = () => {
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(false)
const [userInput, setUserInput] = useState("")
useEffect(() => {
setLoading(true)
axios.get("https://jsonplaceholder.typicode.com/posts")
.then(response => {
setLoading(false)
setPosts(response.data)
})
.catch(error => {
console.log(error)
})
}, [])
const handleChange = (e) => {
setUserInput({ ...userInput, [e.target.name]: e.target.value })
}
const handleSubmit = (e) => {
setLoading(true)
e.preventDefault()
const payload = {
firstName: userInput.firstName,
lastName: userInput.lastName,
password: userInput.password
}
axios.post("https://jsonplaceholder.typicode.com/users", payload)
.then(response => {
console.log(response);
setLoading(false)
})
.catch(error => {
console.log(error)
})
}
return (
<div>
<div style={{ textAlign: "center" }}>
<form onSubmit={handleSubmit}>
<input type="text" name="firstNmae" placeholder="First name" onChange={handleChange} /> <br />
<input type="text" name="lastName" placeholder="Last name" onChange={handleChange} /> <br />
<input type="password" name="password" placeholder="Password" onChange={handleChange} /> <br />
<input type="reset" />
<button>Submit</button>
</form>
</div>
{loading ? <div className={style.spinnerPositioning}><Loader /></div> : <div className={styles.container}>
{posts.map(item => {
return (
<ul key={item.id}>
<li>{item.title}</li>
</ul>
)
})}
</div>}
</div>
)
}
export default App;
If you have to implement Redux into Next.JS this with-redux might solved your problem or Next.JS Faq there is an article there for redux.
You can ask the community of next.js on this link

Reset Password in React

i am creating a mern app. i got stuck in forgot password. i'm able to send a mail for forgot password but when i try to set new password it is not changing password but in postman i was able to change the password but when it comes to react i was not. I know the problem is that i was not able to get token as params .
work in postman but not in when i try in react.
Resetpassword component
import React, { Fragment, useState } from 'react';
import { connect } from 'react-redux';
import { Link, Redirect } from 'react-router-dom';
import { setAlert } from '../../actions/alert';
import { reset } from '../../actions/auth';
import PropTypes from 'prop-types';
const Reset = ({ setAlert, reset }) => {
const [formData, setFormData] = useState({
password: '',
password2: ''
});
const { password, password2 } = formData;
const onChange = e =>
setFormData({ ...formData, [e.target.name]: e.target.value });
const onSubmit = async => {
const token = props.match.params.token;
console.log(token);
if (password !== password2) {
setAlert('password does not matched', 'danger');
} else {
reset({ password, token });
}
};
return (
<Fragment>
<section className='container'>
<h1 className='large text-primary'>RESET PASSWORD</h1>
<p className='lead'>
<i className='fas fa-user' /> Create Your NEW PASSWORD
</p>
<form
className='form'
onSubmit={e => onSubmit(e)}
action='create-profile.html'
>
<div className='form-group'>
<input
type='password'
placeholder='Password'
name='password'
value={password}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input
type='password'
placeholder='Confirm Password'
name='password2'
value={password2}
onChange={e => onChange(e)}
/>
</div>
<input type='submit' className='btn btn-primary' value='Register' />
</form>
<p className='my-1'>
Already have an account? <Link to='/login'>Sign In</Link>
</p>
</section>
</Fragment>
);
};
Reset.propTypes = {
setAlert: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired
};
export default connect(
null,
{ setAlert, reset }
)(Reset);
resetaction.JS
export const reset = ({ password, token }) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ password, token });
try {
const res = await axios.put(
`http://localhost:3000/api/auth/reset/${token}`,
body,
config
);
dispatch({
type: RESET_PASSWORD,
payload: res.data
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
}
};
By only seeing this snippet I assume your problems are following lines:
const Reset = ({ setAlert, reset }) => {
//...
const token = props.match.params.token;
You destructed the whole props argument (into { setAlert, reset }), so in your case props is undefined. You should adapt your code to this:
const Reset = ({ setAlert, reset, match }) => {
//...
const token = match.params.tok

Resources