CodePark/ questions / Hooks, context & providers
START PRACTICINGPRACTICE
Frontend question · Testing › Hooks, context & providers

Many components need a router, a theme and a React Query client. How do you avoid repeating those providers in every test?

export function renderWithProviders(ui: React.ReactElement) {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  function Wrapper({ children }: { children: React.ReactNode }) {
    return (
      <QueryClientProvider client={queryClient}>
        <ThemeProvider>{children}</ThemeProvider>
      </QueryClientProvider>
    );
  }
  return { user: userEvent.setup(), ...render(ui, { wrapper: Wrapper }) };
}

Write a custom render function in a test utility file. Tests import it instead of RTL's original render.

What it does:

  • It wraps the UI in all the app's providers, using RTL's wrapper option.
  • It re-exports everything from @testing-library/react.
  • It accepts options for things tests need to change, such as the initial route or the current user.

Create fresh state on every call:

  • Make a new QueryClient for every test, so cached data doesn't leak between tests.
  • Set retry: false, so error tests don't wait for retries.

Returning the user from userEvent.setup() is a handy extra.

The same wrapper works for renderHook. Tests stay short and focused on behaviour. And when the providers change, you update them in one place.

  • Write a custom render that uses the wrapper option.
  • Re-export RTL from a test-utils file.
  • Create a fresh QueryClient per test, with retries turned off.
  • Accept options for the route, the user and the theme.
  • Use the same wrapper for renderHook.