FrontendJun 10, 20261 min read

Testing Things That Touch the Network

Mocking too much gives you false confidence. Mocking too little gives you a flaky suite. Here's where we landed.


Every test suite that talks to a network eventually faces the same fork: fake the network, or hit something real. We've tried both extremes and settled somewhere in between.

Why full mocking burned us

We used to mock every fetch call with hand-written fixtures. Tests were fast and deterministic, but they drifted from reality — a backend field rename shipped without a single test failing, because the mock still had the old shape.

test("renders the author name", async () => {
  server.use(
    http.get("/api/posts/:id", () => HttpResponse.json(mockPost))
  );
 
  render(<PostPage id="123" />);
  expect(await screen.findByText(mockPost.author.name)).toBeVisible();
});

Contract tests closed the gap

Instead of hand-maintained fixtures, we generate mock responses from the same schema the backend validates against. If the backend changes the shape, the schema changes, and the mock changes with it — no silent drift.

  • Unit and component tests still mock the network, but from schema-derived fixtures.
  • A small number of end-to-end tests run against a real staging API, specifically to catch what the schema can't: auth, latency, and ordering.
  • Flaky end-to-end tests get quarantined immediately, not left red for a week.

The goal was never zero mocking. It was making sure the mocks could not quietly stop telling the truth.

More posts