I am struggled with writing tests to my simple form:
import { useFormik } from 'formik';
import * as yup from 'yup';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import dayjs from 'dayjs';
import Stack from '@mui/material/Stack';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker';
const validationSchema = yup.object({
firstName: yup
.string('Enter your first name')
.required('First name is required'),
lastName: yup
.string('Enter your last name')
.required('Last Name is required'),
email: yup
.string('Enter your email')
.email('Enter a valid email')
.required('Email is required'),
date: yup.string('Enter your date').required('Date is required'),
// .min(Date.now(), 'Start Date must be later than today'),
});
export const AddUserForm = ({ addUser }) => {
const formik = useFormik({
initialValues: {
firstName: '',
lastName: '',
email: '',
date: dayjs(),
},
validationSchema: validationSchema,
onSubmit: (values) => {
addUser({ variables: values });
},
});
return (
<div data-testid="addUserForm">
<form onSubmit={formik.handleSubmit}>
<Stack spacing={3}>
<TextField
fullWidth
id="firstName"
name="firstName"
label="First Name"
value={formik.values.firstName}
onChange={formik.handleChange}
error={formik.touched.firstName && Boolean(formik.errors.firstName)}
helperText={formik.touched.firstName && formik.errors.firstName}
/>
<TextField
fullWidth
id="lastName"
name="lastName"
label="Last Name"
value={formik.values.lastName}
onChange={formik.handleChange}
error={formik.touched.lastName && Boolean(formik.errors.lastName)}
helperText={formik.touched.lastName && formik.errors.lastName}
/>
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>{' '}
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DesktopDatePicker
disablePast
label="Pick Your date"
inputFormat="MM/DD/YYYY"
value={formik.values.date}
onChange={(val) => {
formik.setFieldValue('date', val);
}}
renderInput={(params) => (
<TextField
error={formik.touched.date && Boolean(formik.errors.date)}
helperText={formik.touched.date && formik.errors.date}
{...params}
/>
)}
/>
</LocalizationProvider>
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</Stack>
</form>
</div>
);
};
and tests:
const onSubmit = jest.fn((e) => e.preventDefault());
const addUser = jest.fn();
render(
<MockedProvider mocks={[addUserMock]} addTypename={false}>
<AddUserForm addUser={addUser} />
</MockedProvider>
);
const firstNameInput = screen.getByRole('textbox', { name: /first name/i });
const lastNameInput = screen.getByRole('textbox', { name: /last name/i });
const emailInput = screen.getByRole('textbox', { name: /email/i });
const dateInput = screen.getByRole('textbox', { name: /date/i });
const submitBtn = screen.getByRole('button', { name: /submit/i });
userEvent.type(firstNameInput, 'Cregan');
userEvent.type(lastNameInput, 'Stark');
userEvent.type(emailInput, 'Cregan@Stark.north');
userEvent.type(dateInput, '2024-05-24');
userEvent.click(submitBtn);
await waitFor(() =>
expect(onSubmit).toHaveBeenCalledWith({
firstName: 'Cregan',
lastName: 'Stark',
email: 'Cregan@Stark.north',
date: '20/24/0524',
})
);
});
test('test email validation', async () => {
const addUser = jest.fn();
render(<AddUserForm addUser={addUser} />);
const emailInput = screen.getByRole('textbox', { name: /email/i });
const submitBtn = screen.getByRole('button', { name: /submit/i });
const emailErrorMsg = screen.getByText(/enter a valid email/i);
userEvent.type(emailInput, 'Winter is Coming');
userEvent.click(submitBtn);
await waitFor(() => {
expect(emailErrorMsg).toBeInTheDocument();
});
});```
After i launch tests i have failure with comments: 1 test -
Expected: {"date": "20/24/0524", "email": "Cregan@Stark.north", "firstName": "Cregan", "lastName": "Stark"}
Number of calls: 0
2 test:
TestingLibraryElementError: Unable to find an element with the text: /enter a valid email/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
it looks like React Testing library doesnt recognize click to submit button. I tried to wrapped component with MockProvider, i tried to use fireEvent or userEvent and the result was still the same.
The code are on my GH repository: https://github.com/PrzemekZygmanowski/bh-fe/blob/main/src/components/AddUserForm.jsx https://github.com/PrzemekZygmanowski/bh-fe/blob/main/src/containers/UserForm.jsx https://github.com/PrzemekZygmanowski/bh-fe/blob/main/src/components/AddUserForm.spec.js If You can help me i will be greatful :)
On the first test you are not wiring the mocked onSubmit
into the actual AddUserForm
. Submit seems to be the addUser
prop but this is actually connected to a different mock.
You probably meant:
const onSubmit = jest.fn((e) => e.preventDefault());
render(
<MockedProvider mocks={[addUserMock]} addTypename={false}>
<AddUserForm addUser={onSubmit} />
</MockedProvider>
);
On the second you are getting the screen.getByText(/enter a valid email/i);
and storing it in a local var before the wait. This reference is stale. When waitFor
polls, it just keeps getting the same old stale reference. You need to bring it in scope:
await waitFor(() => {
const emailErrorMsg = screen.getByText(/enter a valid email/i);
expect(emailErrorMsg).toBeInTheDocument();
});
It's not necessary anyway to use waitFor
there. Just use findBy
which will wait internally.
const emailErrorMsg = await screen.findByText(/enter a valid email/i);
expect(emailErrorMsg).toBeInTheDocument();