tezvyn:

Handling Asynchronous UI with Testing Library

AI-drafted, machine-checkedSource: testing-library.comintermediate

Your test shouldn't race your UI. Use async helpers to wait for elements to appear, disappear, or change after an event. This is crucial for testing components that fetch data. The footgun is forgetting `await` on `findBy` or `waitFor` calls.

WHY IT EXISTS Modern UIs are dynamic. A user action, like clicking a button, often triggers asynchronous work like a network request. The UI doesn't update instantly; it updates after the work completes. Tests need a reliable way to wait for these updates to finish before asserting on the final state, avoiding flaky tests that fail due to timing issues.

THE MENTAL MODEL Think of your test as a patient user. A real user waits to see a result after clicking a button. Asynchronous test helpers like waitFor and findBy make your test behave like that patient user, polling the DOM until the expected change has occurred before moving on. It's about synchronizing your test's assertions with the component's asynchronous rendering lifecycle.

HOW IT WORKS RTL provides three main async utilities. All return promises, so you must use await.

First, findBy queries (like findByText or findByRole) are the simplest. They are a shortcut, combining a getBy query with waitFor. They retry finding an element until it appears or a timeout is reached (default 1000ms).

Second, waitFor is a more general-purpose tool. You pass it a callback function containing your expectations. waitFor repeatedly runs this callback until it no longer throws an error, retrying every 50ms by default.

Third, waitForElementToBeRemoved is a specialized wrapper around waitFor. You give it an element or a function that returns an element, and it resolves its promise once that element is no longer in the DOM.

WHEN TO USE IT Use async helpers whenever a DOM change doesn't happen synchronously. This is common after simulating user events that trigger async logic. Use findBy when you need to find an element that is not available right away. Use waitFor for more complex scenarios, like waiting for an assertion that isn't about an element's presence to pass. Use waitForElementToBeRemoved when you explicitly test that something disappears, like a loading spinner.

WHEN NOT TO USE IT Do not use async helpers for things that are already in the DOM. If an element is present after the initial render, use a synchronous query like getByRole. Using findBy for a synchronous element adds unnecessary waiting and can slow down your test suite. The key is to use the right query for the right timing: getBy for present, queryBy for possibly absent, and findBy for eventually present.

ONE CANONICAL EXAMPLE Imagine a button that, when clicked, fetches data and displays it. The test needs to click the button and then wait for the data text to appear.

// Initial state: only the button is visible. const button = screen.getByRole('button', { name: /Load Data/i });

// Simulate the user click, which triggers an async fetch. fireEvent.click(button);

// Using getByText('Success!') would fail immediately. Instead, use // findByText, which waits for the element to appear. const successMessage = await screen.findByText('Success!');

// Now that findByText has resolved, we can safely assert. expect(successMessage).toBeInTheDocument();

Read the original → testing-library.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.