The problem I'm running into is not being able to test if the axios put method was called after clicking the submit button.
I have the following in my __mocks__/axios.js:
const defaultResponse = { data: {} };
const __mock = {
reset() {
Object.assign(__mock.instance, {
get: jest.fn(() => Promise.resolve(defaultResponse)),
put: jest.fn(() => Promise.resolve(defaultResponse)),
post: jest.fn(() => Promise.resolve(defaultResponse)),
delete: jest.fn(() => Promise.resolve(defaultResponse)),
defaults: { headers: { common: {} } },
});
},
instance: {},
};
__mock.reset();
module.exports = {
__mock,
create() {
return __mock.instance;
},
};
My test looks like so:
import React from 'react';
import renderer from 'react-test-renderer';
import { render, fireEvent, screen } from '@testing-library/react';
import mockAxios from 'axios';
import Profile from '../Profile/Profile';
describe('Profile', () => {
beforeEach(() => {
mockAxios.__mock.reset();
});
it('renders correctly', () => {
const tree = renderer.create(<Profile />).toJSON();
expect(tree).toMatchSnapshot();
});
it('submits form successfully', () => {
const { findByLabelText } = render(<Profile />);
const firstName = findByLabelText('first name');
firstName.value = 'Michaux';
const lastName = findByLabelText('last name');
lastName.value = 'Kelley';
const email = findByLabelText('email');
email.value = '[email protected]';
const submit = screen.getByText('Submit');
const { put } = mockAxios.__mock.instance;
put.mockImplementationOnce(() =>
Promise.resolve({
data: {},
})
);
fireEvent.click(submit);
expect(mockAxios.__mock.instance.put).toHaveBeenCalledTimes(1);
expect(mockAxios.__mock.instance.put).toHaveBeenCalledWith('/');
});
});
My form is built using formik:
import React, { Component } from 'react';
import { withFormik } from 'formik';
import { toast } from 'react-toastify';
import { Field } from 'formik';
import * as Yup from 'yup';
import api from '../../api';
import TextInput from '@components/common/forms/TextInput';
import Checkbox from '@components/common/forms/Checkbox';
import CheckboxGroup from '@components/common/forms/CheckboxGroup';
import styles from './profile.module.css';
import commonStyles from '@components/common/common.module.css';
import ForgotPassword from '../ForgotPassword';
import Modal from '@components/Modal/Modal';
const formikEnhancer = withFormik({
validationSchema: Yup.object().shape({
firstName: Yup.string()
.min(2, "C'mon, your first name is longer than that")
.required('First name is required'),
lastName: Yup.string()
.min(2, "C'mon, your last name is longer than that")
.required('Last name is required'),
email: Yup.string()
.email('Invalid email address')
.required('Email is required'),
currentPassword: Yup.string(),
password: Yup.string()
.min(8, 'Password has to be at least 8 characters!')
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\\$%\\^&\\*])/,
'Password must contain at least 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character'
),
confirmPassword: Yup.string().oneOf([Yup.ref('password'), null], 'Passwords must match'),
}),
handleSubmit: (payload, { setSubmitting, setErrors, props }) => {
// TODO: consider putting user in local storage
const { user } = props;
api
.put(`/users/${user.userId}`, payload, {
withCredentials: true,
})
.then(res => {
toast.success('Your user profile was updated successfully', {
position: toast.POSITION.TOP_CENTER,
hideProgressBar: true,
});
})
.catch(err => {
toast.error('Something went wrong', {
position: toast.POSITION.TOP_CENTER,
hideProgressBar: true,
});
});
setSubmitting(false);
},
mapPropsToValues: ({ user }) => ({
...user,
}),
displayName: 'ProfileForm',
});
class Profile extends Component {
modalProps = {
triggerText: 'Forgot Password?',
};
modalContent = <ForgotPassword user={{ email: '' }} {...this.props} />;
render() {
document.title = 'User Profile';
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
isAdmin,
setFieldValue,
} = this.props;
return (
<React.Fragment>
<h2>Profile</h2>
<form onSubmit={handleSubmit}>
<TextInput
id="firstName"
type="text"
label="First Name"
error={touched.firstName && errors.firstName}
value={values.firstName}
onChange={handleChange}
onBlur={handleBlur}
/>
<TextInput
id="lastName"
type="text"
label="Last Name"
error={touched.lastName && errors.lastName}
value={values.lastName}
onChange={handleChange}
onBlur={handleBlur}
/>
<TextInput
id="email"
type="email"
label="Email"
autoComplete="username email"
error={touched.email && errors.email}
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
<TextInput
id="currentPassword"
type="password"
label="Current Password"
autoComplete="current-password"
error={touched.currentPassword && errors.currentPassword}
value={values.currentPassword}
onChange={handleChange}
onBlur={handleBlur}
/>
<div className={styles.forgotPassword}>
<Modal
modalProps={this.modalProps}
modalContent={this.modalContent}
modalButtonClassName={commonStyles.modalLinkButton}
{...this.props}
/>
</div>
<TextInput
id="password"
type="password"
label="New Password"
autoComplete="new-password"
error={touched.password && errors.password}
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
/>
<TextInput
id="confirmPassword"
type="password"
label="Confirm Password"
autoComplete="new-password"
error={touched.confirmPassword && errors.confirmPassword}
value={values.confirmPassword}
onChange={handleChange}
onBlur={handleBlur}
/>
{isAdmin && (
<React.Fragment>
<h3>Roles</h3>
<CheckboxGroup
id="roles"
className={styles.roles}
label="Which of these?"
value={values.roles}
onChange={setFieldValue}
>
<Field
component={Checkbox}
name="roles"
id="admin"
label="Admin"
value="admin"
defaultChecked={values.roles.includes('admin')}
/>
<Field
component={Checkbox}
name="roles"
id="user"
label="User"
value="user"
defaultChecked={values.roles.includes('user')}
/>
<Field
component={Checkbox}
name="roles"
id="family"
label="Family"
value="family"
defaultChecked={values.roles.includes('family')}
/>
<Field
component={Checkbox}
name="roles"
id="friend"
label="Friend"
value="friend"
defaultChecked={values.roles.includes('friend')}
/>
</CheckboxGroup>
</React.Fragment>
)}
<button type="submit" disabled={isSubmitting} className="btn btn-primary">
Submit
</button>
</form>
</React.Fragment>
);
}
}
export default formikEnhancer(Profile);
How do I get the test to tell me if my axios put mock was called?
UPDATE
I can now see three issues to your implementation.
axios.create. You'll have also to mock axios.create with itself:axios.create = jest.fn(() => axios);
__mocks__/axios.js should be like this:const axios = jest.genMockFromModule('axios');
const defaultResponse = { data: {} };
axios.doMockReset = () => {
Object.assign(axios, {
get: jest.fn().mockImplementationOnce(() => Promise.resolve(defaultResponse)),
put: jest.fn().mockImplementationOnce(() => Promise.resolve(defaultResponse)),
post: jest.fn().mockImplementationOnce(() => Promise.resolve(defaultResponse)),
delete: jest.fn().mockImplementationOnce(() => Promise.resolve(defaultResponse)),
defaults: { headers: { common: {} } },
});
}
// Add create here.
// There is no need to call doMockReset, it will be called when test starts
axios.create = jest.fn(() => axios);
module.exports = axios;
handleSubmit asynchronously and Jest does not wait for it to finish. We can use @testing-library/react waitFor to wait for the expect(mockAxios.put).toHaveBeenCalledTimes(1). Also, have the inputs update the values using the fireEvent.change event instead of directly setting the input value:import { render, fireEvent, waitFor } from '@testing-library/react';
...
await waitFor(() => {
fireEvent.change(firstName, {
target: {
value: "Michaux"
}
});
});
...
await waitFor(() => {
expect(mockAxios.put).toHaveBeenCalledTimes(1);
});
import React from 'react';
import renderer from 'react-test-renderer';
import { render, fireEvent, waitFor } from '@testing-library/react';
import mockAxios from 'axios';
import Profile from '../Profile/Profile';
describe('Profile', () => {
beforeEach(() => {
mockAxios.doMockReset();
});
it('renders correctly', () => {
const tree = renderer.create(<Profile />).toJSON();
expect(tree).toMatchSnapshot();
});
it('submits form successfully', async () => {
// Pass a fake user with formik default values for use in mapPropsToValues,
// else React will complaint regarding inputs changed
// from uncontrolled to controlled because of these values initially set to undefined:
// Warning: A component is changing an uncontrolled input of type text to be controlled
const user = { userId: 1, firstName: '', lastName: '', email: '' };
const { getByText, getByLabelText } = render(<Profile user={user} />);
const firstName = getByLabelText(/first name/i);
const lastName = getByLabelText(/last name/i);
const email = getByLabelText(/email/i);
const submit = getByText('Submit');
await waitFor(() => {
fireEvent.change(firstName, {
target: {
value: "Michaux"
}
});
});
await waitFor(() => {
fireEvent.change(lastName, {
target: {
value: "Kelley"
}
});
});
await waitFor(() => {
fireEvent.change(email, {
target: {
value: "[email protected]"
}
});
});
fireEvent.click(submit);
await waitFor(() => {
expect(mockAxios.put).toHaveBeenCalledTimes(1);
})
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With