Async & waitFor
Handle asynchronous UI updates with waitFor, findBy, and act.
waitFor
waitFor retries callback until assertion passes or timeout (default 1000ms). Use when state updates after fetch, timer, or transition without findBy convenience.
Do not pass empty waitFor—include expect inside callback.
await waitFor(() => {
expect(screen.getByText("Loaded")).toBeInTheDocument();
});waitForElementToBeRemoved
Waits for element to leave DOM—loading spinners, toasts, modals. Pass element or query function returning element.
Pair with queryBy for elements that should disappear—null when gone.
await waitForElementToBeRemoved(() => screen.queryByText(/loading/i));
findBy vs waitFor
findByRole("alert") equivalent to waitFor(() => getByRole("alert")). Choose findBy for single element appearance; waitFor for complex multi-assertion conditions.
Increase timeout option for slow CI: findByText("Done", {}, { timeout: 5000 })
- Fake timers require userEvent setup advanceTimer integration
- Avoid waitFor with side effects in callback—assertion only
- Multiple async updates may need single waitFor wrapping all expects
act
React act wraps state updates so tests flush effects before assertions. RTL render and userEvent handle act internally in modern versions.
Manual act needed rarely—async state update in setTimeout you trigger directly without userEvent.
import { act } from "@testing-library/react";
await act(async () => {
jest.advanceTimersByTime(1000);
});Debugging Async Failures
screen.debug() prints DOM at failure point. Log container.innerHTML when element not found. Common bug: missing await on userEvent or findBy.
MSW handler delay simulates slow network; ensure handler registered before render triggers fetch.
- Configure default asyncUtil timeout globally in configure({ asyncUtilTimeout: 5000 })
- Testing Suspense: assert fallback then resolved content with findBy
- Rejecting promises should show error UI—await findByRole alert