Mocking Dependencies

Course 1 · Ch 6
Mocking Dependencies
Where the data in Chapter 5's async tests actually comes from — and why a real network call has no place in a test suite

Chapter 5 tested components that fetch data, using findBy/waitFor to wait for it — but never addressed where that data actually came from during the test. This chapter closes that gap.

The Problem With Real Network Calls in Tests

A real network call in a test is slow, depends on an external service actually being up, and makes it hard to deliberately trigger error cases (a 500 response, a timeout) on demand. Unit and component tests are supposed to be fast and deterministic — directly the speed argument behind Chapter 1's testing pyramid — and a real network dependency violates both.

Mocking fetch/axios Directly

global.fetch = jest.fn().mockResolvedValue({ json: async () => ({ name: "Ada" }), });

This works, but couples the test tightly to how the request is made (specifically fetch, or specifically axios) rather than what the request and response actually are. Swapping the underlying HTTP client later means rewriting every test that mocked it this way.

Mock Service Worker (MSW): The Modern Preferred Approach

MSW intercepts requests at the network level rather than mocking the fetch/axios function itself — the component's real request code runs completely unmodified; only the server's response is faked. This is genuinely more realistic, a direct extension of Chapter 3's guiding principle applied to network behavior, and resilient to swapping HTTP libraries later.

import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; const server = setupServer( http.get("/api/user", () => HttpResponse.json({ name: "Ada" })) ); beforeAll(() => server.listen()); afterAll(() => server.close());

The beforeAll/afterAll lifecycle is Chapter 2's setup/teardown hooks, now managing a mock server's lifetime.

Simulating Error Responses

MSW makes deliberately testing error states trivial — override a handler for one test only:

it("shows an error message when the request fails", async () => { server.use( http.get("/api/user", () => HttpResponse.error()) ); render(<UserProfile />); expect(await screen.findByText("Could not load profile")).toBeInTheDocument(); });

Mocking Timers

Components using setTimeout/setInterval — a debounced search box, a toast that auto-dismisses — would otherwise force tests to actually wait real time, or behave flakily. jest.useFakeTimers() lets a test advance time instantly:

it("dismisses the toast after 3 seconds", () => { jest.useFakeTimers(); render(<Toast message="Saved!" />); expect(screen.getByText("Saved!")).toBeInTheDocument(); act(() => jest.advanceTimersByTime(3000)); expect(screen.queryByText("Saved!")).not.toBeInTheDocument(); });

Mocking Context Providers

A component consuming React Context (e.g. useAuth()) needs that context available to render sensibly — wrapping it in a lightweight, test-only provider with preset fake values avoids needing a real login flow or real backend:

function renderWithProviders(ui, { authValue = { user: { name: "Ada" } } } = {}) { return render( <AuthContext.Provider value={authValue}>{ui}</AuthContext.Provider> ); } renderWithProviders(<Dashboard />);

A reusable helper like this is worth introducing now — Chapters 8–9's more realistic feature tests will lean on it directly.

Manual fetch/axios Mocking vs. MSW

Manual Mocking

Replaces the HTTP client function itself — couples the test to how requests are made.

MSW (preferred)

Intercepts at the network level — the real request code runs unmodified; only the server's response is faked.

MSW Handlers

Define what a URL returns; override per-test with server.use() for error cases.

Fake Timers

jest.useFakeTimers() + advanceTimersByTime() — instant, deterministic time-based tests.

renderWithProviders

A test-only wrapper supplying fake context values instead of a real provider.

On-Demand Error Simulation

Something a real network call can't easily offer — a genuine testing advantage, not just a workaround.

MSW's Handlers Work Beyond Tests Too
Because MSW intercepts at the network level, the same mock handlers can power actual local development — running the app against MSW instead of a real backend — not just automated tests. One set of fake API responses, two genuinely different uses.
Forgetting to Reset MSW Handlers Leaks Between Tests
A handler overridden with server.use(...) for one error-case test stays overridden for every test that runs afterward, unless explicitly reset — typically via afterEach(() => server.resetHandlers()). Forgetting this produces confusing failures in later, seemingly unrelated tests that unexpectedly hit the error response meant for a different test entirely. This is Chapter 2's test-isolation principle (resetting shared state in beforeEach), now applying specifically to network mocks.

Coding Challenges

Challenge 1

Set up an MSW handler for GET /api/products returning a list of two products, and write a test confirming both product names appear on screen after a ProductList component mounts.

📄 View solution
Challenge 2

Write a test using jest.useFakeTimers() and jest.advanceTimersByTime() for a component that shows "Are you still there?" after 60 seconds of inactivity — confirming the message is absent immediately after render and present after advancing time.

📄 View solution
Challenge 3

Explain, with a concrete example, how forgetting server.resetHandlers() in afterEach could cause a test that has nothing to do with errors to fail unexpectedly, if an earlier test in the same file used server.use() to simulate a failed request.

📄 View solution

Chapter 6 Quick Reference

  • Real network calls in tests are slow, flaky, and can't easily simulate error cases on demand
  • Manual fetch/axios mocking — couples tests to how requests are made; works, but fragile to client swaps
  • MSW (preferred) — intercepts at the network level; real request code runs unmodified
  • server.use(...) — override a handler for one test (e.g. to simulate an error); reset with server.resetHandlers() in afterEach
  • jest.useFakeTimers() + advanceTimersByTime() — instant, deterministic testing of setTimeout/setInterval-based behavior
  • renderWithProviders — a reusable helper wrapping render() with fake context values instead of real providers
  • Forgetting to reset MSW handlers leaks state between tests — the same isolation risk Chapter 2's beforeEach was built to prevent
  • Next chapter: Testing Across Frameworks — Vue Test Utils, Angular's TestBed, and Svelte Testing Library