Capítulo 42 de 80

Chapter 42: Testing

Core Idea

Custom hooks wrapping useQuery/useMutation are tested with renderHook (React 18+, from @testing-library/react) plus a per-test QueryClientProvider wrapper, retries disabled so error-path tests don't time out waiting through exponential backoff.

Key Concepts

  • Test isolation: build a fresh QueryClient + QueryClientProvider wrapper per test (or clear a shared one between tests and disallow parallel test execution) so one test's cache state can't leak into another's.
  • React version note: React 17 and earlier need @testing-library/react-hooks + react-test-renderer; React 18+ gets renderHook directly from @testing-library/react, no extra package needed.
  • Disabling retries in tests: set retry: false in the test QueryClient's defaultOptions.queries to avoid tests timing out on the default 3-retry exponential backoff when testing an error path — this only applies as a fallback; a query with an explicit retry count still uses its own value.
  • Jest + gcTime: set gcTime: Infinity (already the server default) if Jest complains about not exiting after a test run, when you've explicitly overridden gcTime to something finite in a test client.
  • Testing network calls: mock the actual HTTP layer (e.g. with nock) rather than mocking queryFn itself, so the test also validates the real request shape; await waitFor(() => expect(result.current.isSuccess).toBe(true)) is the standard pattern for waiting on a hook's query to resolve before asserting on data.
  • Testing infinite queries: mock responses keyed by the page/cursor query param (nock(...).persist().query(true)..., reading page from the mocked request URL), assert the first page loaded, call result.current.fetchNextPage(), then waitFor the accumulated data.pages to include both pages.

Code Examples

const queryClient = new QueryClient({
  defaultOptions: { queries: { retry: false } }, // avoid timeouts testing error paths
})
const wrapper = ({ children }) => (
  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)

const { result } = renderHook(() => useCustomHook(), { wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data).toEqual('Hello')
  • What it demonstrates: the standard test scaffold — isolated QueryClient, retries off, renderHook + waitFor to assert on resolved state.

Key Takeaways

  1. Always disable retries in the test QueryClient when exercising an error path — otherwise the default exponential backoff makes the test slow or flaky against a timeout.
  2. Mock at the network layer (nock or equivalent), not by stubbing queryFn — this actually validates the request your hook makes, not just its wiring.
  3. Build a fresh QueryClient per test (or explicitly clear + serialize test execution) — cache state is exactly the kind of hidden cross-test coupling that causes flaky suites.

Connects To

  • Query Retries: the default backoff behavior this chapter disables for tests.
  • Infinite Queries: the fetchNextPage/data.pages shape exercised in the infinite-scroll test example.