How do you mock a module vs. the network in tests, and why is intercepting HTTP (MSW) usually better than mocking fetch?
Choosing the right seam to fake, and why mocking at the network boundary yields more realistic, less brittle tests.
There are two common things people fake and they sit at different layers. MODULE mocking (jest.mock('./api')) replaces an imported module with a fake implementation — useful for pure code dependencies, feature flags, or a utility you want to control. But when the dependency is the NETWORK, you have a choice of seam. The tempting one is to mock fetch/axios directly (global.fetch = jest.fn().mockResolvedValue(...)) or jest.mock the api client. That works but is brittle and less realistic: your test now asserts against YOUR assumption of what the client returns, bypasses request-building, URL/param/header logic, serialization, error handling, and retries — so 'all mocks pass' can coexist with a broken integration, and every refactor of how you call the API breaks tests. The better approach is to intercept at the NETWORK BOUNDARY with MSW (Mock Service Worker): you declare request handlers ('GET /api/users returns this JSON') and MSW intercepts the actual outbound request, so your app runs its real fetch/axios code end to end — real URLs, headers, status codes — and you only stub the wire response. Benefits: tests are decoupled from HOW you fetch (swap fetch for axios and tests still pass), you exercise real request/response handling, you can model errors and edge cases (500s, timeouts, malformed bodies) declaratively, and the SAME handlers work in unit tests, Storybook, and the browser during development. jest.mock still has its place — mock a module for non-network dependencies, or when you truly want to isolate a unit from a collaborator — but for HTTP, prefer MSW. General principle: mock at the furthest-out boundary you can (the network, the clock), so the most of your real code runs and your tests break only when behavior actually changes.
Testing code that calls an API; deciding between jest.mock and network interception.
// MSW: intercept at the network boundary; real fetch/axios code still runs
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/users', () => HttpResponse.json([{ id: 1, name: 'Ada' }]))
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// Model an error case declaratively for one test:
// server.use(http.get('/api/users', () => new HttpResponse(null, { status: 500 })));
// jest.mock: right for NON-network module dependencies
jest.mock('./featureFlags', () => ({ isEnabled: () => true }));