How to Fix React Query 'No QueryClient set' Error in Jest and React Testing Library Tests
Threat/Impact Level: HIGH | Downtime Risk: HIGH (entire test suite fails, blocks CI pipeline) | Time to Fix: 5 mins
TL;DR
- What broke: Your component calls
useQuerybut the test renderer has noQueryClientProviderin the tree — React Query's context lookup returnsundefinedand throws immediately. - How to fix it: Wrap every render call in a test-scoped
QueryClientProviderwith a freshQueryClientinstance (retry: falseto prevent async bleed). - Shortcut: Use our Client-Side Sandbox below to auto-refactor this — paste your failing test and get corrected output without leaking your code to a third-party server.
The Incident (What Does the Error Mean?)
You'll see this in your Jest output:
Error: No QueryClient set, use QueryClientProvider to set one
at useQueryClient (node_modules/@tanstack/react-query/build/lib/QueryClientProvider.js)
at useBaseQuery (node_modules/@tanstack/react-query/build/lib/useBaseQuery.js)
at useQuery (...)
at YourComponent (src/components/YourComponent.tsx)
useQuery internally calls useQueryClient(), which reads from a React Context. If no QueryClientProvider ancestor exists in the render tree, the context value is undefined, and the hook throws synchronously. The component never mounts. Every test in the file fails. Your CI pipeline is blocked.
The Attack Vector / Blast Radius
This isn't a security vulnerability — it's a CI availability failure. The blast radius:
- All tests in the affected file fail with the same error, masking unrelated regressions.
- Developers assume the component is broken, not the test harness, wasting triage time.
- Flaky retry behavior: If you fix it by sharing a single
QueryClientinstance across tests without clearing it, cached data from test A bleeds into test B, producing non-deterministic failures that are nearly impossible to reproduce locally. - In monorepos with shared test utilities, one missing wrapper in a base render helper propagates this failure to dozens of test files simultaneously.
The secondary trap: wrapping with QueryClientProvider but forgetting retry: false. React Query's default retry: 3 will fire real async retries inside your Jest fake timers environment, causing act() warnings and tests that hang until timeout.
How to Fix It
Basic Fix
Add a createWrapper factory to your test file and pass it to RTL's render.
- import { render, screen } from '@testing-library/react';
+ import { render, screen } from '@testing-library/react';
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { YourComponent } from './YourComponent';
+ const createWrapper = () => {
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
+ return ({ children }: { children: React.ReactNode }) => (
+ <QueryClientProvider client={queryClient}>
+ {children}
+ </QueryClientProvider>
+ );
+ };
it('renders data correctly', async () => {
- render(<YourComponent />);
+ render(<YourComponent />, { wrapper: createWrapper() });
expect(await screen.findByText('Expected Text')).toBeInTheDocument();
});
Critical: Call createWrapper() inside each test or beforeEach — never at module scope. A shared QueryClient instance carries cache between tests.
Enterprise Best Practice
Extract a shared renderWithClient utility into your test setup so every engineer in the org gets the correct behavior by default.
- // No shared test utility exists. Every test file manually wraps (or forgets to).
+ // src/test-utils/renderWithClient.tsx
+ import { render, RenderOptions } from '@testing-library/react';
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+ import React from 'react';
+
+ export function createTestQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ gcTime: 0, // v5: gcTime | v3/v4: cacheTime
+ staleTime: 0,
+ },
+ mutations: {
+ retry: false,
+ },
+ },
+ });
+ }
+
+ export function renderWithClient(
+ ui: React.ReactElement,
+ options?: Omit<RenderOptions, 'wrapper'>
+ ) {
+ const queryClient = createTestQueryClient();
+ const Wrapper = ({ children }: { children: React.ReactNode }) => (
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
+ );
+ return {
+ ...render(ui, { wrapper: Wrapper, ...options }),
+ queryClient,
+ };
+ }
- import { render } from '@testing-library/react';
+ import { renderWithClient } from 'src/test-utils/renderWithClient';
it('renders data correctly', async () => {
- render(<YourComponent />);
+ renderWithClient(<YourComponent />);
expect(await screen.findByText('Expected Text')).toBeInTheDocument();
});
Configure moduleNameMapper or tsconfig paths so 'src/test-utils/renderWithClient' resolves everywhere. Never re-export this from your production code bundle.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. ESLint custom rule or eslint-plugin-testing-library
Add eslint-plugin-testing-library to your ESLint config. While it doesn't directly enforce QueryClientProvider, you can write a custom rule that flags any render( call in test files that doesn't reference QueryClientProvider or your renderWithClient utility.
2. Jest globalSetup / custom render enforcement
Override RTL's render in jest.setup.ts to throw a descriptive error if QueryClientProvider is absent:
+ // jest.setup.ts
+ import { setLogger } from '@tanstack/react-query';
+ setLogger({ log: console.log, warn: console.warn, error: () => {} }); // suppress retry noise
3. Vitest / Jest snapshot of createTestQueryClient options
Write a meta-test that asserts your createTestQueryClient() always returns retry: false. This sounds trivial but catches the case where a developer "helpfully" changes the shared utility to retry: true and breaks 40 tests.
+ it('createTestQueryClient has retry disabled', () => {
+ const client = createTestQueryClient();
+ expect(client.getDefaultOptions().queries?.retry).toBe(false);
+ });
4. Pre-commit hook (Husky + lint-staged)
Run jest --testPathPattern on changed test files before every commit. A No QueryClient set error in a new test file fails the commit before it ever reaches CI.
+ // .husky/pre-commit
+ npx lint-staged
+ // package.json lint-staged config
+ "lint-staged": {
+ "src/**/*.test.{ts,tsx}": ["jest --bail --passWithNoTests --findRelatedTests"]
+ }