For tests that should exercise your application’s normal fetch call, use Mock Service Worker (MSW) to intercept requests when it supports your test runtime. For a narrowly isolated unit test that only needs to check the fetch call’s arguments and result handling, a direct mock such as vi.fn() or jest.fn() can be simpler. Whichever approach you choose, keep an HTTP error response separate from a rejected fetch: a 404 response does not, by itself, take the same path as a network failure.
Choose between MSW and a direct fetch mock
| Approach | What the test replaces | Best fit | Trade-off |
|---|---|---|---|
| MSW request interception | The outgoing request is intercepted; the application can call its ordinary fetch path. | Request-level tests that should model request and response behavior. | Requires reusable handlers and test-runner lifecycle setup. |
| Direct function mock | The fetch function itself is replaced with a mock. | A focused unit test of the arguments passed to fetch or how its result is handled. | You must provide the response methods your code uses, and the test does not exercise the normal request path. |
Vitest’s request-mocking guide recommends MSW for network request mocking and explains that its Node interception uses @mswjs/interceptors. MSW’s Node.js integration guide names both Jest and Vitest. Neither approach is universally best: choose based on what the test needs to prove.
As an Amazon Associate I earn from qualifying purchases.
Set up MSW for a TypeScript test
Keep request handlers separate from runner setup so they can be reused. The following pattern adapts the official MSW and Vitest guidance; it is not tied to a specific installed package version, so check the APIs and imports against the versions in your project.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →1. Define handlers and configure the test lifecycle
Create a setup file such as test/server.ts:
import { afterAll, afterEach, beforeAll } from 'vitest'
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
export const server = setupServer(
http.get('https://api.example.test/items', () =>
HttpResponse.json([{ id: 'item-1' }]),
),
)
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
Register the file in Vitest’s setupFiles configuration. Starting the server before tests, resetting per-test handler overrides afterward, and closing the server at the end prevents overrides from leaking between tests. With onUnhandledRequest: 'error', an unhandled request fails loudly rather than silently becoming an accidental real network call. See the MSW quick start and Vitest’s request-mocking guide.
#1 Best Overall
2. Call the production function in the test
With a handler registered for the endpoint, call the application function that normally makes the request and assert on its returned data or observable state. MSW’s quick start demonstrates making a fetch request against a registered endpoint and checking the parsed JSON result. This keeps the test focused on application behavior while intercepting the request at the network boundary.
Test success, HTTP errors, and network failures separately
These are different inputs with different application behavior. A response can arrive successfully at the transport level while carrying a non-OK status; a network failure means fetch rejects instead of returning a response.
Rank #2
- TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
- TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
- Lightweight, Classic fit, Double-needle sleeve and bottom hem
Successful response
Use a normal JSON response for the success path:
http.get('https://api.example.test/items', () =>
HttpResponse.json([{ id: 'item-1' }]),
)
Assert that the production function returns or exposes the parsed items, depending on its contract.
HTTP response with an error status
Return an actual HTTP response carrying the status you want to exercise. If the production function should treat non-OK responses as errors, it needs to check response.ok or the relevant status and throw an application-level error itself. Fetch does not turn an HTTP 404 or 500 into a rejected promise merely because of that status.
http.get('https://api.example.test/items', () =>
HttpResponse.json({ message: 'Not found' }, { status: 404 }),
)
Then assert the contract your function implements—for example, that it throws a domain-specific error after inspecting the response. Do not use HttpResponse.error() for this case: it represents a network failure, not a response whose status the application can inspect.
Rejected fetch or network failure
Use MSW’s HttpResponse.error() to simulate a network error:
server.use(
http.get('https://api.example.test/items', () =>
HttpResponse.error(),
)
)
The documented Fetch API behavior is a generic TypeError: Failed to fetch. MSW does not let this API customize the message, so assert that the function rejects or enters its error state rather than checking for DNS-, timeout-, or other environment-specific wording. The distinction and limitation are described in MSW’s network errors guide.
Handle caught values safely in TypeScript
In TypeScript, a caught value should be treated as unknown: thrown values are not guaranteed to be Error instances. Narrow the value before reading message so the error path remains safe:
Best Value
try {
return await loadItems()
} catch (error: unknown) {
const message = error instanceof Error
? error.message
: 'Could not load items'
return { error: message }
}
Test both an Error rejection and, if your function is intended to handle arbitrary thrown values, a non-Error value. Keep this separate from the HTTP-status branch: the latter only reaches a catch block if your own code turns that response into a thrown error.
Use a direct mock when the fetch contract is the point
A direct mock is useful when the test needs to verify a narrow contract, such as the URL, method, or headers passed to fetch, without setting up request interception. Configure it to return a promise resolving to an object with the response methods the production code calls, such as json() or text(). Configure a rejected promise for the network-failure case.
Be careful with module mocks that replace the response class as well as fetch. Jest’s 30.0 documentation shows that jest.mock('node-fetch') can also mock Response, leaving methods such as response.text() unavailable; its example retrieves the actual response with jest.requireActual. See Jest’s “Bypassing module mocks” guide. Do not combine a node-fetch response with an unrelated global fetch implementation: use the response type that matches the runtime and module your code actually uses.
Keep runtime-specific behavior in scope
The test environment determines which web APIs and fetch implementation are available. Vitest notes that its Node environment affects available web APIs and presents MSW as a way to mimic network behavior. Confirm your project’s configured environment and globals before relying on a particular implementation.
Abort behavior is another distinct case. The node-fetch documentation describes abort rejections as AbortError and other operational failures as FetchError; that taxonomy applies to node-fetch and should not be assumed for browser fetch or every runtime. See node-fetch error handling.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




