How to Test React Query in React
Testing components that use React Query (TanStack Query) requires a
specific approach because of how it manages asynchronous state and
caching. This article provides a direct, step-by-step guide on how to
test React Query in your React applications. You will learn how to
configure a custom test renderer with QueryClientProvider,
mock network requests using Mock Service Worker (MSW), and write clean,
reliable unit tests for components and custom hooks.
1. Set Up the QueryClientProvider Wrapper
React Query hooks like useQuery and
useMutation must be rendered within a
QueryClientProvider. To avoid repeating this setup in every
test, create a reusable wrapper function using React Testing
Library.
It is crucial to create a new QueryClient instance for
every single test to prevent test cross-contamination caused by cached
data. Additionally, disabling retries in your test configuration
prevents Jest or Vitest from timing out when simulating failed
requests.
import React from 'react';
import { render } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
retry: false, // Prevents Jest from timing out on failed queries
},
},
});
export function renderWithClient(ui) {
const testQueryClient = createTestQueryClient();
const { rerender, ...result } = render(
<QueryClientProvider client={testQueryClient}>{ui}</QueryClientProvider>
);
return {
...result,
rerender: (rerenderUi) =>
rerender(
<QueryClientProvider client={testQueryClient}>{rerenderUi}</QueryClientProvider>
),
};
}2. Mock Network Requests with MSW
Instead of mocking the useQuery hook itself, mock the
network layer. This ensures you are testing the actual behavior of your
component. Mock Service Worker (MSW) is the recommended tool for
intercepting network requests in tests.
First, set up your MSW handlers to mock the API response:
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('https://api.example.com/data', () => {
return HttpResponse.json({ id: 1, name: 'Test Product' });
}),
];
export const server = setupServer(...handlers);Configure your test setup file (e.g., setupTests.js) to
start the server before your tests run, and clean up afterward:
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());3. Testing a Component
With the wrapper and network mocks in place, you can test components
that use React Query. Use findBy queries from React Testing
Library to handle the asynchronous nature of the state changes.
Here is an example component and its test:
// ProductComponent.jsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';
export function ProductComponent() {
const { data, isLoading, error } = useQuery({
queryKey: ['product'],
queryFn: () => fetch('https://api.example.com/data').then((res) => res.json()),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error loading data</div>;
return <h1>{data.name}</h1>;
}// ProductComponent.test.jsx
import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithClient } from './utils';
import { ProductComponent } from './ProductComponent';
import { server } from './mocks/server';
import { http, HttpResponse } from 'msw';
describe('ProductComponent', () => {
it('renders loading state initially, then displays data', async () => {
renderWithClient(<ProductComponent />);
// Assert loading state
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Assert success state with fetched data
const heading = await screen.findByRole('heading', { name: 'Test Product' });
expect(heading).toBeInTheDocument();
});
it('renders error state on API failure', async () => {
// Override the handler for this specific test
server.use(
http.get('https://api.example.com/data', () => {
return new HttpResponse(null, { status: 500 });
})
);
renderWithClient(<ProductComponent />);
const errorMessage = await screen.findByText('Error loading data');
expect(errorMessage).toBeInTheDocument();
});
});4. Testing Custom Hooks
If you encapsulate your React Query logic in custom hooks, you can
test them directly using renderHook from React Testing
Library. Pass the QueryClientProvider wrapper inside the
options object.
import { renderHook, waitFor } from '@testing-library/react';
import { useQuery } from '@tanstack/react-query';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
function useCustomHook() {
return useQuery({
queryKey: ['customData'],
queryFn: () => fetch('https://api.example.com/data').then((res) => res.json()),
});
}
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
test('should fetch and return data in custom hook', async () => {
const { result } = renderHook(() => useCustomHook(), {
wrapper: createWrapper(),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual({ id: 1, name: 'Test Product' });
});