Mocking axios.post response triggered by button in react component - reactjs

I would like to test (with RTL and jest) the api.post (with axios) triggered by a button in my react component bellow. It's about a login display with 2 inputs fields (email and password) and 2 buttons (enter and register). Both the enter and the register buttons called asynchronous functions.
// src > pages > Login
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { saveToken } from '../app/slices/tokenSlice';
import { saveUserData } from '../app/slices/userSlice';
import api from '../services/api';
function Login() {
const [loginState, setLoginState] = useState({
email: '',
password: '',
});
const navigate = useNavigate();
const dispatch = useDispatch();
const handleChange = ({ target: { name, value } }) => {
setLoginState({
...loginState,
[name]: value,
});
};
const enterUser = async () => {
await api.post('/login', { ...loginState })
.then((response) => {
dispatch(saveToken(response.data.token));
dispatch(saveUserData(loginState));
navigate('/home');
})
.catch((error) => {
alert(error.message);
});
};
const registerUser = async () => {
api.post('/user', loginState)
.then(() => {
alert('Usuário cadastrado com sucesso!');
})
.catch((error) => {
alert(error.message);
});
};
return (
<div>
<label htmlFor="email">
Email:
<input
id="email"
name="email"
onChange={handleChange}
placeholder="Email"
type="text"
value={loginState.email}
/>
</label>
<label htmlFor="password">
Senha:
<input
id="password"
name="password"
onChange={handleChange}
type="password"
value={loginState.password}
/>
</label>
<button
name="btnEnterUser"
type="button"
onClick={() => enterUser()}
>
Entrar
</button>
<button
name="btnRegisterUser"
type="button"
onClick={() => registerUser()}
>
Cadastrar
</button>
</div>
);
}
export default Login;
// src > services > api.js
import axios from 'axios';
const { BASE_URL } = process.env;
const api = axios.create({
baseURL: BASE_URL,
});
api.defaults.headers.post['Content-Type'] = 'application/json;charset=utf-8';
api.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
export default api;
How can i develop a test with mock data related to "registerUser" and "enterUser" functions. I have tried many options with jest.fn(), jest.spyOn() and userEvent.click(btnRegister), where btnRegister is the element which i getByRole as you can check in my test file bellow:
// src > tests > login.test.js
import React from 'react';
import '#testing-library/jest-dom';
import { screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import Login from '../pages/Login';
import renderWithRouter from './renderWithRouter';
import api from '../services/api';
describe('Componente Login', () => {
let inputEmail;
let inputPassword;
let btnRegister;
let btnEnter;
const userEmail = 'mariana#gmail.com';
const userPassword = '123456';
const tokenMock = {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjA4MWYyYzBhMWFhNzFmYjhjZjU2NjAiLCJlbWFpbCI6Im1hcmlhbmFAZ21haWwuY29tIiwiaWF0IjoxNjQ1MzIyMjU1LCJleHAiOjE2NDUzNDAyNTV9.TIgJFIzg1W0bisvJ3CfRsVCZr3kbKn13_NBN-Ah1U1w',
};
const userRegisteredResponseMock = {
user:
{
email: 'vitao#gmail.com',
id: '6211a1d3eb25fc2418dec05a',
},
};
beforeAll(() => {
renderWithRouter(<Login />);
inputEmail = screen.getByLabelText(/email/i);
inputPassword = screen.getByLabelText(/senha/i);
btnRegister = screen.getByRole('button', { name: /cadastrar/i });
btnEnter = screen.getByRole('button', { name: /entrar/i });
userEvent.type(inputEmail, userEmail);
userEvent.type(inputPassword, userPassword);
});
it('Registro de usuário com sucesso', async () => {
// ************** DOESNT WORK **********************
// api.post = jest.fn().mockImplementation(() => {
// Promise.resolve(userRegisteredResponseMock);
// });
// api.post = jest.fn(() => Promise.resolve(userRegisteredResponseMock));
// api.post = jest.fn().mockResolvedValue(userRegisteredResponseMock);
// jest.spyOn(Login, 'enterUser');
// ****************************************************
userEvent.click(btnRegister);
expect(<"REGISTER USER" FUNCTION>).toBeCalledTimes(1);
});
});
I also have tried created "mocks" folder as jest documentation has mentioned in this link: https://jestjs.io/docs/manual-mocks , but without success.

in order to force jest using the mocked module, the jest.mock() function should be called.
import api from '../services/api';
...
jest.mock('../services/api');
...
api.post.mockResolvedValue(userRegisteredResponseMock);
it's also possible to mock axios module itself. furthermore, there's a jest-mock-axios npm module designed to achieve the behaviour.

Related

React: A non-serializable value was detected in the state

I'm trying to make a simple login/logout feature in my app using firebase auth rest API, I'm using redux to let user log in and logout, the user get registered perfectly in the firebase but when I hit Signup & Login button of the form, I'm getting this error 👇
With redux toolkit I'm wondering what's going wrong with my initialState of login function.
Here is my code: -
//login-store.js
const { configureStore, createSlice } = require("#reduxjs/toolkit");
const userAuth = createSlice({
name: "login",
initialState: {
token: "",
isLoggedIn: false,
login: (token) => {},
logout: () => {},
},
reducers: {
logginOut(state) {
state.isLoggedIn = false;
state.logout = () => {
localStorage.removeItem("userLoginToken");
};
},
loggingIn(state) {
state.isLoggedIn = true;
state.token = localStorage.getItem("userLoginToken");
state.login = (token) => {
return localStorage.setItem("userLoginToken", token);
};
},
},
});
const authStore = configureStore({
reducer: userAuth.reducer,
});
export const userAuthAction = userAuth.actions;
export default authStore;
And here I'm having my login and signup feature. Also there is one more problem, whenever I click New User button below the submit button, I immediately get the alert popup (written with comment below) I don't know how am I sending fetch request while switching the form...
//Login.js
import React, { useRef, useState } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { userAuthAction } from "../store/login-store";
import classes from "./pages.module.css";
export default function Login() {
const dispatch = useDispatch();
const [isLogin, setIsLogin] = useState(true);
const navigate = useNavigate();
const emailInput = useRef();
const passwordInput = useRef();
const switchAuthTextHandler = () => {
setIsLogin((prevState) => !prevState);
};
const loginAuthHandler = (e) => {
e.preventDefault();
const enteredEmailValue = emailInput.current.value;
const enteredPasswordValue = passwordInput.current.value;
let authUrl;
if (isLogin) {
// when logging in
authUrl =
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyB3Mbv38Ju8c9QedQzqX3QvufTCOXhkU0c";
} else {
// when signing up
authUrl =
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=AIzaSyB3Mbv38Ju8c9QedQzqX3QvufTCOXhkU0c";
}
fetch(authUrl, {
method: "POST",
body: JSON.stringify({
email: enteredEmailValue,
password: enteredPasswordValue,
returnSecureToken: true,
}),
headers: {
"Content-type": "application/json",
},
})
.then((res) => {
if (res.ok) {
return res.json();
} else {
return res.json().then((data) => {
// getting alert popup immediately after switching the form
alert(data.error.message);
});
}
})
.then((data) => {
dispatch(userAuthAction.loggingIn(data.idToken));
navigate("/");
console.log(data);
})
.catch((err) => {
console.error(err.message);
});
};
return (
<div className={classes.loginWrapper}>
<form onSubmit={loginAuthHandler}>
<h4>{isLogin ? "Login" : "Signup"}</h4>
<div className={classes.form_group}>
<label htmlFor="email">Email</label>
<input type="email" id="email" ref={emailInput} />
</div>
<div className={classes.form_group}>
<label htmlFor="password">Password</label>
<input type="password" id="password" ref={passwordInput} />
</div>
<div className={classes.form_group}>
<button type="submit">{isLogin ? "Login" : "Signup"}</button>
</div>
<div className={classes.form_group}>
<button className={classes.newUser} onClick={switchAuthTextHandler}>
{isLogin ? "New User?" : "Already have account"}
</button>
</div>
</form>
</div>
);
}
Error while siging up the new user:-
We should not store function references in the redux store. They are not serializable, and states should be serializable in redux state.

React jest login form test

Hi i am new developer testing platform. I have a problem but I did not find a solution or work it with correct way. I am trying to login component test with to parameter by Inputs. Firstly I filled these are userEvent.type. After I am clicking my button. And when I was waiting my method that call by onSubmitForTest in one time , I am facing an error like fallowing image.
What is the reason of this ? How can I solve my problem ? Thanks for your helps.
My Login.tsx component:
import React, { FC, useState } from "react";
import { useTranslation } from "react-i18next";
import Input from "../../components/Input";
import InputPassword from "../../components/Input/InputPassword";
import ButtonLoading from "../../components/Button/ButtonLoading";
import { GetLoginInfo, ILoginRequest } from "../../store/actions/loginActions";
interface ILoginState {
emailorUsername: string;
password: string;
}
const initialState = {
emailorUsername: "",
password: "",
};
interface IProps {
onSubmitForTest: (items: any) => void
}
const Login: FC<IProps> = ({ onSubmitForTest }) => {
const { t } = useTranslation();
const [state, setstate] = useState<ILoginState>(initialState);
const onChange = (key: string, value: string | number) => {
setstate({ ...state, [key]: value });
};
const handleLogin = async () => {
const loginRequest: ILoginRequest = {
emailOrUsername: state.emailorUsername,
password: state.password,
returnUrl: "",
};
const response = await GetLoginInfo(loginRequest);
if (response.isSucceed) { } else { }
};
const renderLoginPart = () => {
return (
<div className="flex">
<Input
name="emailorUsername"
label={t("emailorUsername")}
value={state.emailorUsername}
onChange={(val: any) => onChange("emailorUsername", val)}
/>
<InputPassword
name="password"
label={t("password")}
value={state.password}
onChange={(val: any) => onChange("password", val)}
/>
<ButtonLoading
text={t("login")}
variant="contained"
onClick={() => {
if (onSubmitForTest) {
const loginRequestItemForTest = {
emailOrUsername: "testUsername",
password: "testPassword",
};
onSubmitForTest(loginRequestItemForTest)
}
handleLogin()
}}
dataTestid={"login-button-element"}
/>
</div>
);
};
return <div className="">{renderLoginPart()}</div>;
};
export default Login;
My index.test.js :
import React from 'react'
import { render, screen, waitFor } from "#testing-library/react"
import LoginPage from "../index"
import userEvent from "#testing-library/user-event"
const onSubmit = jest.fn()
beforeEach(()=>{
const { } = render(<LoginPage />)
onSubmit.mockClear()
})
test('Login form parametre olarak doğru data gönderme testi', async () => {
const eMail = screen.getByTestId('text-input-element')
const password = screen.getByTestId('password-input-element')
userEvent.type(eMail, "fillWithTestUsername")
userEvent.type(password, "fillWithTestPassword")
userEvent.click(screen.getByTestId('login-button-element'))
await waitFor(()=>{
expect(onSubmit).toHaveBeenCalledTimes(1)
})
})
beforeEach(()=>{
render(<LoginPage onSubmitForTest={onSubmit} />)
})
Please try doing this in beforeEach. If this still doesn't work you can try replacing toHaveBeenCalledTimes with toBeCalledTimes like below
await waitFor(()=>{
expect(onSubmit).toBeCalledTimes(1)
})

How to test amplify auth signup in react using Jest + Enzyme?

I need to test the register method along with the amplify auth signup which is written in my action file.
import history from "../utils/history.helper";
import { alertInfo } from "../utils/common.helper";
import { Auth } from "aws-amplify";
export const AuthAction = {
register,
};
function register(signUpData) {
return async (dispatch) => {
const {
username,
password,
company_name,
country_name,
designation,
} = signUpData;
try {
const signUpResponse = await Auth.signUp({
username,
password,
attributes: {
"custom:company_name": company_name,
"custom:country_name": country_name,
"custom:designation": designation,
},
});
alertInfo("success", "Please verify the mail,for successfull login");
history.push("/login");
} catch (error) {
alertInfo("error", error.message);
}
};
}]
This action file I'm calling from my signup component
import React from "react";
import { useForm } from "react-hook-form";
import { useDispatch } from "react-redux";
import { yupResolver } from "#hookform/resolvers/yup";
import content from "./content";
import { AuthInputField } from "../../common/FieldComponent";
import { AuthAction } from "../../../actions/auth.action";
import { signupSchema } from "../../common/Validation";
const SignupForm = () => {
const dispatch = useDispatch();
const { register, handleSubmit, errors } = useForm({
resolver: yupResolver(signupSchema),
});
const [buttonDisable, setButtonDisable] = React.useState(false);
function onSubmit(signUpData) {
setButtonDisable(true);
dispatch(AuthAction.register(signUpData));
}
return (
<div className="container" data-test="signUpContainer">
<h4>Welcome</h4>
<p>Please resigter to your account.</p>
<form data-testid="submit" onSubmit={handleSubmit(onSubmit)}>
{content.inputs.map((input, index) => {
return (
<AuthInputField
key={index}
name={input.name}
label={input.label}
placeholder={input.placeholder}
type={input.type}
register={register}
error={(errors && errors[input.name] && errors[input.name]) || {}}
/>
);
})}
<input
type="submit"
className="btn btn-primary button"
name="submit"
value={`Submit`}
role="submit"
disabled={buttonDisable}
/>
</form>
</div>
);
};
export default SignupForm;
I'm trying to find a way for testing the "Auth.signup" but didn't find any specific solution.
After spending so many hours, finally wrote these test cases for the above question.
import { register } from "./auth.action";
import mockData from "./__mocks__/mockData";
import { Auth } from "./__mocks__/aws-amplify";
import history from "../utils/history.helper";
jest.mock("./auth.action", () => {
return { register: jest.fn(() => mockPromise) };
});
jest.mock("aws-amplify");
describe("Signup action", () => {
beforeEach(() => {
register.mockClear();
});
test("Check register function have been called or not", async () => {
register();
expect(register).toHaveBeenCalled();
expect(register).toMatchSnapshot();
});
test("Check args passed in function are valid or not", () => {
expect(mockData.signupData).not.toBeNull();
expect(mockData.signupData).toMatchObject({
username: "Abhinav02#getnada.com",
});
expect(mockData.signupData).toHaveProperty("company_name", "Ces");
expect(mockData.signupData).toHaveProperty("country_name", "India");
expect(mockData.signupData).toHaveProperty("designation", "SD");
expect(mockData.signupData).toHaveProperty("password", "Password#123");
expect(mockData.signupData).toMatchSnapshot();
});
test("Amplify auth is called or not", () => {
Auth.signUp(mockData.signupData);
expect(Auth.signUp).toHaveBeenCalled();
expect(Auth.signUp).toMatchSnapshot();
});
test("history is pushed", () => {
const pushSpy = jest.spyOn(history, "push");
pushSpy("/login");
expect(pushSpy).toHaveBeenCalled();
expect(pushSpy.mock.calls[0][0]).toEqual("/login");
});
});
I have written the amplify auth test case in mock file.
// in __mocks__/aws-amplify.js
export const Auth = {
currentSession: jest.fn(() => Promise.resolve()),
signUp: jest.fn(() => Promise.resolve()),
signIn: jest.fn(() => Promise.resolve()),
};
Hope it helps others as well who are looking for the same.

React waitforelement not working with async call

I'm currently trying a simple login form with an async call in React using TypeScript and classes.
My component looks like this:
import * as React from 'react';
import { LoginService } from './services/LoginService';
interface CredentialsState {
userName: string,
password: string,
isLoggedIn: boolean,
loginAttempted: boolean
}
interface CustomEvent {
target: HTMLInputElement
}
export class Login extends React.Component<{}, CredentialsState> {
state: CredentialsState = {
password: "",
userName: "",
isLoggedIn: false,
loginAttempted: false
};
private loginService: LoginService = new LoginService();
private async handleSubmit(event: React.SyntheticEvent) {
event.preventDefault();
const loginResponse = await this.loginService.login(
this.state.userName,
this.state.password
);
console.log('Login result ' + loginResponse);
this.setState({
loginAttempted: true,
isLoggedIn: loginResponse
});
}
private setPassword(event: CustomEvent) {
this.setState({ password: event.target.value });
}
private setUserName(event: CustomEvent) {
this.setState({ userName: event.target.value });
}
render() {
let loginLabel;
if (this.state.loginAttempted) {
if (this.state.isLoggedIn) {
loginLabel = <label>Login successful</label>
} else {
loginLabel = <label>Login failed</label>
}
}
return (
<div>
<form data-test="login-form" onSubmit={e => this.handleSubmit(e)}>
<input data-test="login-input" name="login" value={this.state.userName} onChange={e => this.setUserName(e)} /><br />
<input data-test="password-input" name="password" value={this.state.password} onChange={e => this.setPassword(e)} type="password" /><br />
<input data-test="submit-button" type="submit" value="Login" /><br />
</form>
{loginLabel}
</div>
)
}
}
It works just fine, but I also want to test it using #testing-library/react
My test looks like this:
import { fireEvent, waitForElement } from '#testing-library/react';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils';
import { Login } from './login';
import { LoginService } from './services/LoginService';
describe('Login component tests', () => {
let container: HTMLDivElement;
const loginServiceSpy = jest.spyOn(LoginService.prototype, 'login');
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
act(() => {
ReactDOM.render(<Login />, container);
});
})
afterEach(() => {
document.body.removeChild(container as HTMLDivElement);
container.remove();
});
it('renders correctly status label', async () => {
loginServiceSpy.mockResolvedValueOnce(false);
const button = container.querySelectorAll('input')[2];
fireEvent.click(button);
const label = await waitForElement(() => {
container.querySelector('label');
}); // THIS query times out and test fails
expect(label).toBeInTheDocument();
});
});
How can I make this async test work? Without the async functionality, the container.querySelector('label') works.
Thanks!
Figured it out myself. I was misusing waitForElement.
The proper way to use it is:
const label = await waitForElement(() =>
container.querySelector('label')
)

Errors when mocking firebase within a React app using Jest and React Testing Library

I want to test a component which calls firebase.auth().sendPasswordResetEmail() on click, so I want to test that firebase is being called onClick, but am not sure how to implement - I don't want to call the api in a test.
I would love some guidance on mocking/intercepting firebase calls in general.
I am using React with Jest and React Testing Library.
This is the component in question:
import React from 'react'
import { withFirebase } from '../Firebase'
interface PFProps {
firebase: firebase.app.App
}
interface S {
email: string
}
interface Error {
message?: string
}
const PasswordForget = ({ firebase }: PFProps) => {
const initialState = { email: '' }
const stateReducer = (state: S, update: { [x: string]: string }) => ({
...state,
...update,
})
const [state, dispatch] = React.useReducer(stateReducer, initialState)
const [error, setError] = React.useState<Error>()
const isValid = () => validator.isEmail(state.email)
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault()
if (!isValid()) {
return setError({ message: messages.emailIsInvalid })
}
firebase
.auth()
.sendPasswordResetEmail(state.email)
.then(success => console.log(success))
.catch(error => setError(error))
dispatch(initialState)
}
const handleChange = ({
currentTarget: { name, value },
}: React.ChangeEvent<HTMLInputElement>) => {
setError(undefined)
dispatch({ [name]: value })
}
return (
<>
<form onSubmit={handleSubmit} data-testid="form" noValidate>
{error && error.message && <FormErrorPanel message={error.message} />}
<Input
type="email"
name="email"
data-testid="pwf-email"
value={state.email}
onChange={handleChange}
placeholder="Enter your email address"
/>
<Button>Reset password</Button>
</form>
</>
)
}
const PasswordForgetLink = () => (
<p>
<Link to={ROUTES.PASSWORD_FORGET}>Forgotten password</Link>
</p>
)
export { PasswordForgetLink }
export default withFirebase(PasswordForget)
This is how I am currently trying to mock firebase:
import React from 'react'
import '#testing-library/jest-dom/extend-expect'
import { render, cleanup, fireEvent } from '#testing-library/react'
import { FirebaseContext } from '../../Firebase'
import firebase from '../../Firebase'
import PasswordForget from '../index'
jest.mock('../../Firebase/firebase', () => {
return {
auth: jest.fn(() => ({
sendPasswordResetEmail: jest.fn(() => Promise.resolve()),
})),
}
})
afterEach(cleanup)
const setup = () => {
const utils = render(
<FirebaseContext.Provider value={firebase}>
<PasswordForget />
</FirebaseContext.Provider>,
)
const form = utils.getByTestId('form')
const emailInput = utils.getByTestId('pwf-email') as HTMLInputElement
const h1 = utils.getByText(/Forgotten Password/i)
return {
h1,
form,
emailInput,
...utils,
}
}
test('should call sendPasswordResetEmail method when the form is submitted with a valid email', () => {
const { form, emailInput } = setup()
const email = 'peterparker#foo.com'
fireEvent.change(emailInput, { target: { value: email } })
expect(emailInput.value).not.toBeNull()
fireEvent.submit(form)
expect(firebase.auth().sendPasswordResetEmail).toHaveBeenCalledTimes(1)
})
But I am getting the error:
Expected mock function to have been called one time, but it was called zero times.
Does anyone know what I am doing wrong?
Many thaks
If you're using create-react-app, you can try to add a "mocks" folder inside the "src" folder, then add a "firebase.js" inside "mocks" folder
in "firebase.js":
const mockFirebase = {
auth: jest.fn(() => mockFirebase ),
sendPasswordResetEmail: jest.fn(() => Promise.resolve(fakeResponse))
};
export { mockFirebase as default };
"fakeResponse" is your expected response.
then delete these in your test.js:
jest.mock('../../Firebase/firebase', () => {
return {
auth: jest.fn(() => ({
sendPasswordResetEmail: jest.fn(() => Promise.resolve()),
})),
}
})
check the Jest official site for more info: https://jestjs.io/docs/en/manual-mocks

Resources