TanStack Query + React Testing Library hooks and components (Vitest + jsdom): working versions (tested 2026-10-15)
Test React components and hooks that use TanStack Query v5 with Testing Library on jsdom: fresh QueryClient per test, retries off, mocked fetchers, mutations with invalidation.
passed3 passed · 0 failed2026-10-15 13:17 UTC- Status
- passed
- Last passed
- 2026-10-15 13:17 UTC
- Tests
- 3 / 3
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38740169402
The code
Exactly the files that passed. Your agent can pull them with get_recipe("react-query-testing-library") (install), or see them on GitHub.
{
"name": "react-query-testing-library",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run --reporter=default --reporter=json --outputFile=test-report.json"
},
"dependencies": {
"@tanstack/react-query": "5.104.3",
"react": "19.3.1",
"react-dom": "19.3.1"
},
"devDependencies": {
"@types/node": "24.19.2",
"vitest": "5.0.4",
"@testing-library/react": "16.3.3",
"@testing-library/dom": "10.4.2",
"jsdom": "30.1.3",
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0"
}
}
import { defineConfig } from "vitest/config";
export default defineConfig({ esbuild: { jsx: "automatic" }, test: { environment: "jsdom" } });
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
export type Api = { list: () => Promise<string[]>; add: (t: string) => Promise<void> };
export function useTodos(api: Api) {
return useQuery({ queryKey: ["todos"], queryFn: api.list });
}
export function Todos({ api }: { api: Api }) {
const qc = useQueryClient();
const todos = useTodos(api);
const add = useMutation({ mutationFn: api.add, onSuccess: () => qc.invalidateQueries({ queryKey: ["todos"] }) });
if (todos.isPending) return <p>Loading...</p>;
if (todos.isError) return <p role="alert">Error: {todos.error.message}</p>;
return (
<div>
<ul>{todos.data.map((t) => <li key={t}>{t}</li>)}</ul>
<button onClick={() => add.mutate("new")}>Add</button>
</div>
);
}
import { describe, it, expect, vi, afterEach } from "vitest";
import { render, screen, fireEvent, renderHook, waitFor, cleanup } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { ReactNode } from "react";
import { Todos, useTodos, type Api } from "../src/Todos";
afterEach(cleanup);
// New client per test, retries off, so failures show immediately and cache never leaks between tests.
function wrapper() {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } });
return ({ children }: { children: ReactNode }) => <QueryClientProvider client={qc}>{children}</QueryClientProvider>;
}
describe("Todos", () => {
it("hook resolves data", async () => {
const api: Api = { list: vi.fn().mockResolvedValue(["a"]), add: vi.fn() };
const { result } = renderHook(() => useTodos(api), { wrapper: wrapper() });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(["a"]);
});
it("renders the error state", async () => {
const api: Api = { list: vi.fn().mockRejectedValue(new Error("boom")), add: vi.fn() };
render(<Todos api={api} />, { wrapper: wrapper() });
expect(await screen.findByRole("alert")).toHaveProperty("textContent", "Error: boom");
});
it("mutation invalidates and refetches", async () => {
const items = ["a"];
const api: Api = { list: vi.fn(async () => [...items]), add: vi.fn(async (t: string) => { items.push(t); }) };
render(<Todos api={api} />, { wrapper: wrapper() });
await screen.findByText("a");
fireEvent.click(screen.getByText("Add"));
await screen.findByText("new");
expect(api.list).toHaveBeenCalledTimes(2);
});
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Astro 7 component rendering tests with the Container API (Vitest)
Render Astro components to HTML in Vitest with the Container API and getViteConfig, checking props, slots and conditional markup.
Hono + Drizzle ORM + SQLite REST API (Vitest)
CRUD REST API in Hono backed by Drizzle ORM on better-sqlite3, with Zod validation, tested with Vitest and app.request().
Hono + Valibot request validation (Vitest)
Validate JSON bodies and query strings in Hono with Valibot schemas via @hono/valibot-validator, with custom error responses, tested with Vitest.
i18next + react-i18next: plurals, interpolation and SSR rendering
Configure i18next with resources, plurals, context and escaping, and render react-i18next components to HTML on the server, with per-request instances.
All @tanstack/react-query recipesAll react recipesAll @testing-library/react recipesAll vitest recipesAll jsdom recipes