Lesson 20 / 25
Context, Providers and Routing
A custom render wrapper.
Render with the same providers as the app
Real components depend on context: a theme, an auth session, a data-fetching client such as TanStack Query, i18n and a router. Rendering them bare throws or behaves unrealistically. Write a custom render that wraps the UI in the app's providers via the wrapper option, accepts options such as an initial route or user, and re-exports everything from Testing Library, so tests import render from your test utils. Create a fresh store or query client per test to keep isolation (and turn off retries in tests so error states appear quickly). For routing, use an in-memory router such as React Router's MemoryRouter with initialEntries; package names differ between React Router versions, so check the docs for yours.
test-utils.tsx with a wrapper
Assumes React Router and TanStack Query; adapt to your stack.
// src/test/test-utils.tsx
import type { ReactElement, ReactNode } from 'react';
import { render, type RenderOptions } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router-dom';
import { AuthProvider, type User } from '../auth';
type Options = Omit<RenderOptions, 'wrapper'> & { route?: string; user?: User | null };
export function renderWithProviders(ui: ReactElement, { route = '/', user = null, ...options }: Options = {}) {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
function Wrapper({ children }: { children: ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider initialUser={user}>
<MemoryRouter initialEntries={[route]}>{children}</MemoryRouter>
</AuthProvider>
</QueryClientProvider>
);
}
return render(ui, { wrapper: Wrapper, ...options });
}
export * from '@testing-library/react';
// in a test
// renderWithProviders(<AccountPage />, { route: '/account', user: buildUser({ name: 'Asha' }) });A stage with the lights on
Rendering a component without its providers is like rehearsing a play in an empty warehouse with no lights or props. The custom render builds the real stage once, so every rehearsal looks like opening night.
Quick check: Why create a new QueryClient (or store) inside the custom render?
- Because providers cannot be reused in production
- So cached data and state do not leak from one test into another
- To make tests run in a real browser
- To increase coverage numbers
Answer
So cached data and state do not leak from one test into another — Fresh state per test keeps tests isolated.