test("shows an error for an invalid email", async () => {
const user = userEvent.setup();
const onSubmit = jest.fn();
render(<SignUpForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), "not-an-email");
await user.click(screen.getByRole("button", { name: /sign up/i }));
expect(await screen.findByText(/enter a valid email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/email/i)).toBeInvalid();
expect(onSubmit).not.toHaveBeenCalled();
});
Test it like a user: find fields by label, type and click with user-event, and assert what the user sees.
How to find things:
- Find fields with
getByLabelText. - Find the button with
getByRole('button', { name: /sign up/i }). - Use user-event for typing and clicking.
The important paths to cover:
- Submitting empty or invalid input shows the right error messages. It doesn't call the submit handler or the API.
- Fixing the input clears the errors.
- Valid input calls
onSubmitwith the right values, or sends the right request (checked through MSW). Then the success state shows. - If the button is disabled while submitting, assert
toBeDisabled().
Assert on what users see, such as error text or role='alert'. Also assert accessibility attributes, like aria-invalid through toBeInvalid().
Don't assert on the form library's internal state. Then you could switch from React Hook Form to something else without rewriting the tests.
- Find fields by label and role, and interact with user-event.
- Invalid input shows errors, and submit is not called.
- Valid input submits the correct values, then shows the success state.
- Test the pending state, for example a disabled button.
- Don't assert on the form library's internal state.
Read more
- user-event intro (https://testing-library.com/docs/user-event/intro)testing-library.com
- About queries (https://testing-library.com/docs/queries/about)testing-library.com