React cookbook · Intermediate
Test a data-fetching component with MSW
Write reliable tests for a component that loads data, including loading and error states.
Approach
Mock HTTP with Mock Service Worker so the component's real fetch/TanStack Query code runs. Create a fresh QueryClient per test with retries off, use findBy* queries to wait for async UI, and override handlers per test for error cases.
Solution
import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, expect, it } from "vitest";
import { UserList } from "./UserList";
const server = setupServer(
http.get("/api/users", () => HttpResponse.json([{ id: "1", name: "Ada Lovelace" }])),
);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
function renderWithQuery(ui: React.ReactElement) {
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return render(<QueryClientProvider client={client}>{ui}</QueryClientProvider>);
}
it("renders users", async () => {
renderWithQuery(<UserList />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
expect(await screen.findByText("Ada Lovelace")).toBeInTheDocument();
});
it("shows an error message when the API fails", async () => {
server.use(http.get("/api/users", () => HttpResponse.json({ detail: "boom" }, { status: 500 })));
renderWithQuery(<UserList />);
expect(await screen.findByRole("alert")).toHaveTextContent(/couldn't load users/i);
});
Pitfalls
- A shared QueryClient leaking cached data across tests.
- Retries making error tests slow.
- Unhandled requests silently passing — set
onUnhandledRequest: 'error'.
Variations
- Reuse the same MSW handlers in the browser for local development.
- Playwright component testing for real-browser behavior.
Further reading: MSW: Node.js integration
Reference: https://mswjs.io/docs/integrations/node
More React recipes
- Add optimistic updates to a todo list
- Build an accessible modal dialog
- Build infinite scroll with TanStack Query
- Call a cookie-authenticated API with CSRF protection
- Debounce a search input in React
- Handle errors gracefully with error boundaries
- Implement dark mode that follows the system
- Poll a long-running job's status