Introduction
Have you ever implemented a required form check using React Hook Form + Chakra UI, tested it with the Jest Testing Library, and found that the error message you see in the UI isn't visible in the test?
This issue is often caused by asynchronous rendering and text matching. Here, we'll summarize common causes and solutions with minimal code and test examples to help you avoid the same problem.
Problem
Test Fails:
Unable to find an element with the text: ID is required
Failing Test Example:
The DOM is searched immediately
expect(screen.getByText("ID is required")).toBeInTheDocument();
Why Does This Happen? (Common Cause)
Asynchronous Rendering
React Hook Form validation updates the state after submit/blur, which means it's reflected in the DOM. It doesn't appear immediately after clicking.Conditional Rendering
Chakra UI's<FormErrorMessage>
is only rendered when there is an error. If there is no error, it does not exist in the DOM.Strict text matching
An exact match may miss wraparound characters such as<p>
or subtle differences in wording.
Solution (This is a stable solution for now)
1) Asynchronous Wait: Use findBy/waitFor
Wait until the element appears
expect(await screen.findByText(/ID is required/i)).toBeInTheDocument();
Or
await waitFor(() => {
expect(screen.getByText(/ID is required/i)).toBeInTheDocument();
});
- Match text gently
Use regular expressions (/.../i) to be robust to case and subtle differences.
Consistent wording between UI and test (eliminate discrepancies such as "required" vs. "ID is required")
- Use userEvents whenever possible
Firing events that mimic actual user actions (type, click, tab, clear, etc.) allows blur/change to occur naturally, resulting in highly reproducible tests.
const user = userEvent.setup();
await user.type(screen.getByLabelText(/ID/i), "");
await user.click(screen.getByRole("button", { name: /Register/i }));
expect(await screen.findByText(/ID is required/i)).toBeInTheDocument();
Full Code (Minimal Example)
Component
<FormControl isInvalid={!!errors.userId}>
<FormLabel>ID</FormLabel>
<Input {...register("userId", { required: "ID is required" })} />
<FormErrorMessage>{errors.userId?.message}</FormErrorMessage>
</FormControl>
Test
test("Display error if ID is not entered", async () => {
render(<SampleForm />);
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: /Register/i }));
expect(await screen.findByText(/ID is required/)).toBeInTheDocument();
});
Summary
"It appears in the UI, but not in the test" = Asynchronous rendering is the cause
Use findByText / waitFor to wait until it appears in the DOM
Top comments (0)