Why Mock HTTP Calls in Tests?
Unit and integration tests that hit a real API are slow, flaky when the network or third-party service is down, and can hit rate limits or incur cost on paid endpoints; they also make test outcomes depend on external data that can change over time. Mocking HTTP requests removes the real network from the equation entirely, letting you assert on exactly how your code handles a given response shape, including edge cases like empty arrays or malformed payloads that would be hard to reliably reproduce against a live service.
Cricket analogy: Simulating a rain-affected match on a practice ground instead of waiting for real weather mirrors mocking HTTP requests to avoid network flakiness and unpredictable delays in tests.
Mocking fetch and axios Directly
For axios, jest.mock('axios') combined with axios.get.mockResolvedValueOnce({ data: {...} }) is the most direct approach, letting you configure exactly what the client 'receives' back per test. For the native fetch API, since it's a global rather than an importable module, you typically assign global.fetch = jest.fn() and configure it to resolve with an object shaped like a real Response — including ok, status, and a json() method returning a Promise — since your code likely calls response.json() to parse the body.
Cricket analogy: Scripting a scoreboard operator to always display 'India 250/4' regardless of the actual live feed mirrors mocking axios.get to always resolve with a fixed score payload.
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/user/:id', (req, res, ctx) => {
return res(ctx.status(200), ctx.json({ id: 1, name: 'Ada' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('fetches a user successfully', async () => {
const user = await fetchUser(1);
expect(user.name).toBe('Ada');
});Using Interceptor Libraries: nock and msw
nock intercepts outgoing HTTP requests at Node's http/https module level, which is powerful because it works regardless of which HTTP client library your code uses internally — you don't need to know or mock axios versus fetch specifically, just declare the expected request and response. msw (Mock Service Worker) takes a similar network-level interception approach but works in both Node (via setupServer) and real browsers (via a Service Worker), meaning application code makes genuinely unmodified fetch/axios calls that are intercepted transparently, which many teams prefer because it keeps tests closer to production behavior.
Cricket analogy: Using a broadcast delay buffer that intercepts and replays commentary, versus a full studio mockup, mirrors nock intercepting real network calls versus msw simulating requests at a higher level.
msw (Mock Service Worker) intercepts requests at the network layer using Node's http/https interceptors (or a real Service Worker in browsers), so your application code calls fetch/axios completely unmodified — unlike jest.mock('axios'), which replaces the module import itself.
Testing Error and Timeout Scenarios
A well-tested HTTP integration covers more than the happy path: axios.get.mockRejectedValueOnce(new Error('Network Error')) simulates a connection failure, resolving with { status: 500 } or { status: 404 } (ok: false for fetch) simulates server errors and missing resources, and with msw you can define a handler that returns ctx.status(500) or delays a response to simulate a timeout. Testing these paths verifies your error-handling code — retries, fallback UI, user-facing error messages — actually works rather than only being exercised by the successful case.
Cricket analogy: Forgetting to reset a scripted scoreboard after simulating a rain delay could leave a fake score displayed during the next real match, mirroring mocks leaking between tests.
If you use server.use() in msw to override a handler for one test, always call server.resetHandlers() in afterEach — otherwise that one-off override silently applies to every subsequent test in the file.
- Mocking HTTP calls keeps tests fast, deterministic, and independent of real network availability or third-party API rate limits.
- jest.mock('axios') combined with mockResolvedValue/mockRejectedValue is the simplest way to fake a specific library's request methods.
- For native fetch, assigning global.fetch = jest.fn() lets you control resolved Response-like objects per test.
- nock intercepts outgoing HTTP requests at Node's http/https module level, which works even for libraries you don't directly mock.
- msw (Mock Service Worker) intercepts requests at the network boundary in both Node and the browser, so application code calling fetch/axios stays completely unmodified.
- Simulating error and timeout scenarios (mockRejectedValue, 500 responses, network errors) is essential for testing an app's failure-handling paths, not just the happy path.
- Always reset HTTP mocks/handlers between tests (afterEach) to prevent one test's stubbed response from silently affecting another.
Practice what you learned
1. Which approach intercepts requests without requiring you to mock the axios or fetch module directly?
2. How do you simulate a rejected/failed request with a mocked axios method?
3. What's a key advantage of msw over directly mocking the axios module?
4. What must you call between tests when using msw's setupServer to avoid handler leakage?
5. Why mock HTTP requests in unit tests instead of hitting a real API?
Was this page helpful?
You May Also Like
Mocking Modules with jest.mock()
Understand how jest.mock() replaces entire modules with automatic or manual mocks, and how to combine that with jest.requireActual for partial mocking.
Mock Functions with jest.fn()
Learn how to create, configure, and inspect Jest mock functions to isolate units under test and verify how dependencies are called.
Mocking Timers
Learn how to use Jest's fake timers to control setTimeout, setInterval, and Date deterministically instead of waiting on real wall-clock time in tests.