Chapter 4 of 4
Mocking and Setup
Faking the network, testing hooks, and a checklist for good tests.
Mocking fetch by hand couples your tests to how the request is made. Mock Service Worker intercepts at the network level, so the component uses its real data layer.
import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";
const server = setupServer(
http.get("/api/user", () =>
HttpResponse.json({ id: 1, name: "Ada" })
)
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test("shows an error when the request fails", async () => {
server.use(
http.get("/api/user", () => new HttpResponse(null, { status: 500 }))
);
render(<Profile />);
expect(await screen.findByRole("alert")).toHaveTextContent(/could not load/i);
});Testing a custom hook
import { renderHook, act } from "@testing-library/react";
test("useToggle flips the value", () => {
const { result } = renderHook(() => useToggle(false));
expect(result.current[0]).toBe(false);
act(() => {
result.current[1]();
});
expect(result.current[0]).toBe(true);
});A custom render with providers
function renderWithProviders(ui, options) {
function Wrapper({ children }) {
return (
<QueryClientProvider client={new QueryClient()}>
<ThemeProvider value="light">{children}</ThemeProvider>
</QueryClientProvider>
);
}
return render(ui, { wrapper: Wrapper, ...options });
}A checklist
- Query by role first; fall back to test ids only when nothing else works.
- Use
user-event, andawaitevery call. - Use
queryByfor absence,findByfor anything asynchronous. - Mock at the network boundary, not at the module boundary.
- One behaviour per test, with a name that says what the user should see.
- If a refactor breaks a test but not the app, rewrite the test.
Choose the right query type
Complete queryFor so it returns 'findBy' for async, 'queryBy' for absence checks, and 'getBy' otherwise.