पाठ 18 / 25

Async UI, Forms and Hooks

findBy, waitFor and renderHook.

Wait for the UI, not for time

Components that fetch data or validate asynchronously update after the test's current line. Use findBy... to wait for something to appear, waitForElementToBeRemoved to wait for a spinner to disappear, and waitFor(() => expect(...)) for other assertions that become true later; waitFor retries the callback until it stops throwing or times out. Keep a single assertion inside waitFor, and do not put side effects such as clicks inside it, because it may run many times. Never use arbitrary sleeps. For custom hooks, renderHook (exported by @testing-library/react in current versions) renders the hook in a tiny test component and exposes result.current; wrap state updates in act. Often, though, testing the hook through a component that uses it is more realistic.

Loading state, data and a hook

API calls are handled by MSW handlers from the setup file.

import { act, render, renderHook, screen, waitForElementToBeRemoved } from '@testing-library/react';
import { expect, it } from 'vitest';
import { http, HttpResponse } from 'msw';
import { server } from './test/server';
import { OrderList } from './OrderList';
import { useCounter } from './useCounter';

it('shows orders after loading', async () => {
  render(<OrderList />);
  await waitForElementToBeRemoved(() => screen.queryByText(/loading/i));
  expect(screen.getAllByRole('listitem')).toHaveLength(2);
});

it('shows an error message when the API fails', async () => {
  server.use(http.get('/api/orders', () => new HttpResponse(null, { status: 500 })));
  render(<OrderList />);
  expect(await screen.findByRole('alert')).toHaveTextContent(/could not load orders/i);
});

it('increments the counter hook', () => {
  const { result } = renderHook(() => useCounter(5));
  act(() => result.current.increment());
  expect(result.current.count).toBe(6);
});

Heed the act() warning

A warning that an update was "not wrapped in act(...)" usually means the component updated after your test stopped watching. Await the visible result (findBy, waitFor) instead of silencing the warning.

त्वरित जाँच: What should go inside a waitFor callback?

  • An assertion that will eventually pass, with no side effects such as clicks
  • A user.click call so it retries the click
  • A fixed sleep of 2 seconds
  • The render call
Answer

An assertion that will eventually pass, with no side effects such as clicks — waitFor may run the callback many times.