0

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, '[email protected]');
    userEvent.type(dateInput, '2024-05-24');
    userEvent.click(submitBtn);
  
    await waitFor(() =>
      expect(onSubmit).toHaveBeenCalledWith({
        firstName: 'Cregan',
        lastName: 'Stark',
        email: '[email protected]',
        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": "[email protected]", "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 :)

1

1 Answer 1

1

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();
Sign up to request clarification or add additional context in comments.

4 Comments

ok thank You @Adam Thomas, on the second case, Your advice helped me. on the first case i change onsubmit in props to addUser and it works too. But now i have a problem with testing Date Picker...
Would you mind marking this as accepted? :). Happy to answer new question if you post one.
sure, ps. i solved problem with date picker, thank You for Your help :)
Nice! I recommand leaning towards the "findBy" methods and awaiting them. Tends to be less flakey because it waits for stuff rather than assuming its already there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.