Mocking fetch() lets you test request-handling code without making real network calls. This Jest tutorial covers successful responses, HTTP errors, rejected requests, and restoring mocks between tests. It uses Node.js for the test environment and explains what changes when your tests need a real browser.
Key Takeaways
- A
fetch()call normally resolves for HTTP errors such as 404 or 500; checkresponse.okorresponse.statusyourself. - Mock a successful response, an HTTP error response, and a rejected request as separate cases.
- Restore spies after each test so one test's mock behavior does not leak into another.
- These examples run in Node.js with its built-in Fetch API. A simulated DOM environment is not the same as a real browser.
Set up a Node.js test
The example uses Jest and Node.js 18 or later, which provides global fetch and Response. Install Jest in your project:
npm install --save-dev jest
Add a test script to package.json:
{
"scripts": {
"test": "jest"
}
}
Make the test environment explicit in jest.config.cjs:
module.exports = {
testEnvironment: "node"
};
This example uses CommonJS modules. The code under test accepts an API base URL, so it builds an absolute URL that works with Node's Fetch API. Replace the example host with your application's configured API origin when adapting the function.
// getUser.js
async function getUser(id, apiBaseUrl) {
const url = new URL(`/users/${encodeURIComponent(id)}`, apiBaseUrl);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
module.exports = { getUser };
Mock a successful response
A Response instance gives the mock realistic Fetch API behavior. Use mockResolvedValueOnce() to return it for one call:
const { getUser } = require("./getUser");
afterEach(() => {
jest.restoreAllMocks();
});
test("returns a user from a successful response", async () => {
const fetchMock = jest.spyOn(globalThis, "fetch").mockResolvedValueOnce(
new Response(JSON.stringify({ id: "42", name: "Ada" }), {
status: 200,
headers: { "Content-Type": "application/json" }
})
);
await expect(getUser("42", "https://api.example.test")).resolves.toEqual({
id: "42",
name: "Ada"
});
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(fetchMock.mock.calls[0][0].href).toBe(
"https://api.example.test/users/42"
);
});
The mock intercepts the global fetch; it does not send a request to api.example.test. The test checks both the returned data and the URL passed to fetch.
Test HTTP errors separately
An HTTP error response is still a response: fetch() resolves, and the code decides whether the status is acceptable. Since getUser() checks response.ok, test that behavior with a non-success status:
test("throws when the server responds with an HTTP error", async () => {
jest.spyOn(globalThis, "fetch").mockResolvedValueOnce(
new Response("Unavailable", { status: 503 })
);
await expect(getUser("42", "https://api.example.test")).rejects.toThrow(
"HTTP 503"
);
});
This is different from a rejected request. A server response with a status such as 404 or 503 does not, by itself, make fetch() reject.
Test a rejected request
A rejected promise represents a failure such as a network error. It does not contain an HTTP status or response body:
test("propagates a rejected fetch request", async () => {
jest
.spyOn(globalThis, "fetch")
.mockRejectedValueOnce(new TypeError("Network failure"));
await expect(getUser("42", "https://api.example.test")).rejects.toThrow(
"Network failure"
);
});
If your application translates network failures into its own error type or message, assert that behavior instead. Avoid treating every rejected request as an HTTP error: the server may never have returned a response.
Clean up mocks between tests
jest.restoreAllMocks() in afterEach() restores spies created with jest.spyOn(). That helps prevent a mock implementation from affecting later tests. For a manually assigned mock, save the original function and restore it yourself; restoreAllMocks() does not restore arbitrary assignments.
Keep mock responses specific to each test. mockResolvedValueOnce() and mockRejectedValueOnce() make the intended behavior clear and avoid leaving a persistent response configured for later calls.
Node.js tests versus browser tests
The examples above run in Node.js, not in a browser. Node 18 and later provide global fetch and Response; older Node versions may need a Fetch implementation or a different test setup. Confirm the APIs available in your project's runtime.
A Jest jsdom environment simulates browser-related APIs for tests, but it is not a real browser and does not guarantee that fetch is available or behaves exactly as it does in one. If the test needs actual browser behavior—such as browser CORS enforcement, page integration, or browser-specific request handling—run it in a browser test environment and use that environment's documented request-interception mechanism. For a unit test of your own request-handling function, mocking its fetch dependency is usually sufficient.
Common Mistakes
- Expecting HTTP errors to reject automatically: Check
response.okorresponse.statusin application code. - Using a relative URL with Node's real
fetch: Node requires an absolute URL. The example builds one withnew URL(); browser code often uses relative URLs instead. - Mocking the wrong environment:
globalThis.fetchmust exist forjest.spyOn()to spy on it. The stated Node version provides it; other environments may require setup or a different approach. - Leaving mocks installed: Restore spies after each test, especially when tests share a global environment.
- Testing only the happy path: Cover HTTP failures and rejected requests because they represent different outcomes.
Best Practices
- Mock at the boundary where your code calls
fetch, and keep each test focused on one outcome. - Assert meaningful behavior—returned data, thrown errors, and important request details—rather than only asserting that
fetchwas called. - Use real
Responseobjects when their status and body behavior matter; use simpler mock objects only when the code under test needs no more than those fields. - Keep browser integration tests separate from Node unit tests when browser behavior is part of what you need to verify.
Documentation note: Live documentation retrieval was not available while preparing this article, so the official Jest and Fetch API documentation could not be checked in this session. The examples are limited to established Jest APIs and Node's built-in Fetch globals; verify compatibility with the exact versions in your project.
Conclusion
Mock fetch() with a Jest spy to control the response your code receives, then test success, HTTP failure, and rejected-request behavior independently. Run the examples in Node.js 18 or later and restore spies after each test to keep the suite isolated.
