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, and await every call.
  • Use queryBy for absence, findBy for 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.