DEV Community

Kazutora Hattori
Kazutora Hattori

Posted on

What to do when you get "Unable to find an element with the text: ..." when using React Hook Form + Testing Library

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
Enter fullscreen mode Exit fullscreen mode

Failing Test Example:

The DOM is searched immediately

expect(screen.getByText("ID is required")).toBeInTheDocument();
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

Or

await waitFor(() => {
expect(screen.getByText(/ID is required/i)).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode
  1. 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")

  1. 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();
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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)