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
wrapperoption. - 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
QueryClientfor 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
wrapperoption. - Re-export RTL from a test-utils file.
- Create a fresh
QueryClientper test, with retries turned off. - Accept options for the route, the user and the theme.
- Use the same wrapper for
renderHook.
Read more