When writing frontend or Node.js tests, mocking HTTP requests is practically mandatory. But choosing a mock solution is overwhelming: MSW, nock, fetch-mock, jest-fetch-mockβ¦ each has a different API style, interception level, and environment support. If you work on both Cloudflare Workers and Node.js projects, you’ll find the mock APIs are completely different and test code can’t be shared.
msw-fetch-mock solves exactly this: it provides the same API style as undici’s MockAgent and Cloudflare Workers’ fetchMock, with MSW handling network-level interception underneath. One API, every environment.
Why Existing Solutions Fall Short
Here are the 6 mainstream HTTP mock solutions today:
| Solution | npm Weekly Downloads | Interception Level | Node native fetch | Browser | Maintenance |
|---|---|---|---|---|---|
| MSW | ~10.5M | Service Worker / Node internals | β | β | Active |
| nock | ~5.5M | Node http module | β | β | Active |
| fetch-mock | ~1.0M | Replaces globalThis.fetch | β | β | Active |
| jest-fetch-mock | ~1.3M | Replaces global.fetch | β | β | Abandoned |
| vitest-fetch-mock | ~240K | Replaces globalThis.fetch | β | β | Active |
| undici MockAgent | Built-in | undici Dispatcher | β | β | Node core |
Each has clear limitations:
- MSW is the most complete, but verbose. Every endpoint needs
http.get(url, resolver), and it lackstimes(),persist(),assertNoPendingInterceptors()β essentials for testing. - nock is the Node.js veteran with a clean API, but doesn’t support Node 18+ native
fetch. Native fetch uses undici, which bypasses thehttpmodule entirely. - fetch-mock replaces
globalThis.fetchdirectly β it works, but it’s not network-level interception, so behavior may differ from production. - jest-fetch-mock hasn’t been updated in 6 years. No URL matching β responses are returned by call order only.
- vitest-fetch-mock is jest-fetch-mock ported to Vitest. Same limitation: no URL matching.
- undici MockAgent is the native Node.js solution, but doesn’t work in browsers.
Where msw-fetch-mock Fits
msw-fetch-mock doesn’t build a mock engine from scratch. It stands on MSW’s shoulders β using MSW for network-level interception (Service Worker in browser, @mswjs/interceptors in Node) β and wraps it with an undici-style API.
The architecture has three layers:
| |

The key is the single catch-all handler. MSW’s standard approach registers one handler per endpoint, but in browser environments this causes Service Worker timing issues (each worker.use() requires SW communication). msw-fetch-mock registers just one http.all('*', ...) catch-all, running all matching logic in the main thread, avoiding Service Worker round-trip latency.
Quick Start API
| |
Basic Setup
| |
Chain Builder
| |
Behavior Control
| |
Call History
| |
Net Connect Control
| |
Full Comparison of All 6 Solutions
Feature Comparison
| Feature | msw-fetch-mock | MSW | nock | fetch-mock | jest-fetch-mock | undici MockAgent |
|---|---|---|---|---|---|---|
| URL pattern matching | β | β | β | β | β | Partial |
| Method matching | β | β | β | β | β | β |
| Header matching | β | β | β | β | β | β |
| Body matching | β | β | β | β | β | β |
times(n) | β | β | β | β | β | β |
persist() | β | β | β | β | β | β |
delay(ms) | β | β | β | β | β | β |
assertNoPendingInterceptors() | β | β | β | β | β | β |
| Call history + filtering | β | β | β | β | Partial | β |
| Network error simulation | β | β | β | β | β | β |
| GraphQL support | β | β | β | β | β | β |
| Record & Replay | β | β | β | β | β | β |
Environment Support
| Environment | msw-fetch-mock | MSW | nock | fetch-mock | jest-fetch-mock | undici MockAgent |
|---|---|---|---|---|---|---|
| Jest | β | β | β | β | β | β |
| Vitest | β | β | β | β | β | β |
| Node native fetch | β | β | β | β | β | β |
| Node http/axios | β | β | β | β | β | β |
| Browser | β | β | β | β | β | β |
| Cloudflare Workers API compat | β | β | β | β | β | β |
Interception Level
The interception level determines how closely mock behavior matches production:
| Solution | Interception Level | Description |
|---|---|---|
| MSW (browser) | Service Worker | Zero patching, closest to production |
| MSW (Node) | Node internals + undici | Extends ClientRequest, not monkey-patching |
| msw-fetch-mock | Same as MSW | MSW under the hood |
| nock | http.request | Monkey-patches Node http module |
| fetch-mock | globalThis.fetch | Replaces the fetch function |
| jest-fetch-mock | global.fetch | Replaces fetch with jest.fn() |
| undici MockAgent | undici Dispatcher | Replaces undici’s dispatcher |
The Real Advantages of msw-fetch-mock
msw-fetch-mock has three concrete advantages over the alternatives:
1. One API, Three Environments
The APIs for undici MockAgent, Cloudflare Workers fetchMock, and msw-fetch-mock are nearly identical:
| |
If your code runs on both Node.js and Cloudflare Workers, your test mocks can share the same patterns β just change the import.
2. MSW’s Interception Quality + undici’s API Simplicity
MSW has the highest interception quality available (Service Worker in browser, @mswjs/interceptors in Node), but its API is verbose:
| |
Plus, the features MSW lacks β times(), persist(), assertNoPendingInterceptors(), call history filtering β msw-fetch-mock has them all.
3. Works Alongside Existing MSW Setups
If your project already has an MSW server (e.g., for Storybook or integration tests), msw-fetch-mock can plug right in:
| |
No need to tear down your existing MSW setup. No conflicts.
4. Works Without MSW Too
If you don’t want to install MSW, msw-fetch-mock has a native mode that patches globalThis.fetch directly:
| |
Same API, just a different interception level. When you’re ready to migrate to MSW, change the import path.
Recommendations by Use Case
| Your Need | Recommended Solution |
|---|---|
| Cross Node.js + Cloudflare Workers, unified mock API | msw-fetch-mock |
| Already using MSW, but find the API too verbose | msw-fetch-mock (plug into existing server) |
| Full-stack project, need browser + Node mocking | MSW or msw-fetch-mock |
| Node.js only + axios/http | nock |
| Node.js only + native fetch, no MSW | undici MockAgent |
| Vitest simple scenarios, no URL matching needed | vitest-fetch-mock |
| Jest simple scenarios | fetch-mock (avoid jest-fetch-mock β abandoned) |
msw-fetch-mock’s sweet spot: you want MSW’s interception quality with undici/Cloudflare’s clean API, plus times(), persist(), and assertNoPendingInterceptors() for proper test lifecycle management.
