react-testing-libraryuser-event

Testing Library's `user-event` button click event must be called twice to get the same results as with one `fireEvent` click


I have a form created with React Hook Form that has a required text input field and a submit button. If the user doesn't enter a value to the field before submitting the form, an error message (from formState: { errors } of RHF's useForm hook) is shown to the user in a div element that is otherwise hidden.

This code checks this functionality correctly in my test, using (React) Testing Library and its fireEvent function:

render(<MyPage />);
const submitButton = screen.getByRole("button", { name: "Submit form" });

await act(async () => {
  fireEvent.click(submitButton);
});

const errorMessage = screen.queryByText("This value is required");
expect(errorMessage).toBeVisible();

However, I thought I'd start using the @testing-library/user-event library in my tests to better simulate an actual user.

Following its documentation, I tried the following:

const user = userEvent.setup();
render(<MyPage />);
const submitButton = screen.getByRole("button", { name: "Submit form" });

await user.click(submitButton);

const errorMessage = screen.queryByText("This value is required");
expect(errorMessage).toBeVisible();

This doesn't work, i.e. the query doesn't find any elements.

Neither does this:

await act(async () => {
  user.click(submitButton);
});

This DOES work:

await act(async () => {
  await user.click(submitButton);
});

but gives a warning "The current testing environment is not configured to support act".

Interestingly what works best is calling the event twice (and not wrapping it in act):

await user.click(submitButton);
await user.click(submitButton);

It seems that actually any two events make the test pass without warnings, e.g.

await user.click(submitButton);
await user.tab();

I also have @testing-library/jest-dom imported in the file which I guess might cause the act-related warning.

So I know there are a lot of moving parts here that might cause the issue, but does anyone see any logical reason why I have to call these user-event events twice to trigger the same effect that I get with a single fireEvent call?

Just to be clear, the error message is shown correctly – and immediately after the button click – in the browser when I test this manually.


Solution

  • Testing for appearance and disappearance with React Testing Library is sometimes tricky to get the timing fully right. I've had success waiting for the appearance of the element in similar tests with userEvent.

    https://testing-library.com/docs/guide-disappearance/#2-using-waitfor

    render(<MyPage />);
    const submitButton = screen.getByRole("button", { name: "Submit form" });
    
    userEvent.click(submitButton);
    
    await waitFor(() => {
      expect(screen.queryByText("This value is required")).toBeVisible()
    })