msw-fetch-mock 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,14 +18,12 @@ npm install -D msw-fetch-mock msw
18
18
 
19
19
  ## Quick Start
20
20
 
21
- ```typescript
22
- import { setupServer } from 'msw/node';
23
- import { FetchMock } from 'msw-fetch-mock';
21
+ ### Standalone (Cloudflare migration)
24
22
 
25
- const server = setupServer();
26
- const fetchMock = new FetchMock(server);
23
+ ```typescript
24
+ import { fetchMock } from 'msw-fetch-mock';
27
25
 
28
- beforeAll(() => fetchMock.activate());
26
+ beforeAll(() => fetchMock.activate({ onUnhandledRequest: 'error' }));
29
27
  afterAll(() => fetchMock.deactivate());
30
28
  afterEach(() => fetchMock.assertNoPendingInterceptors());
31
29
 
@@ -42,11 +40,63 @@ it('mocks a GET request', async () => {
42
40
  });
43
41
  ```
44
42
 
43
+ ### With an existing MSW server
44
+
45
+ If you already use MSW, pass your server to share a single interceptor:
46
+
47
+ ```typescript
48
+ import { setupServer } from 'msw/node';
49
+ import { http, HttpResponse } from 'msw';
50
+ import { FetchMock } from 'msw-fetch-mock';
51
+
52
+ const server = setupServer(http.get('/api/users', () => HttpResponse.json([{ id: 1 }])));
53
+ const fetchMock = new FetchMock(server);
54
+
55
+ beforeAll(() => server.listen());
56
+ afterAll(() => server.close());
57
+ afterEach(() => {
58
+ server.resetHandlers();
59
+ fetchMock.assertNoPendingInterceptors();
60
+ });
61
+ ```
62
+
63
+ > **Note:** Only one MSW server can be active at a time. If another server is already listening, standalone `activate()` will throw an error guiding you to use `new FetchMock(server)` instead.
64
+
65
+ ## Unhandled Requests
66
+
67
+ By default `activate()` uses `'error'` mode — unmatched requests cause `fetch()` to reject. This includes requests to **consumed** interceptors (once a one-shot interceptor has been used, its handler is removed from MSW).
68
+
69
+ ```typescript
70
+ // Reject unmatched requests (default)
71
+ fetchMock.activate();
72
+ fetchMock.activate({ onUnhandledRequest: 'error' });
73
+
74
+ // Log a warning but allow passthrough
75
+ fetchMock.activate({ onUnhandledRequest: 'warn' });
76
+
77
+ // Silently allow passthrough
78
+ fetchMock.activate({ onUnhandledRequest: 'bypass' });
79
+
80
+ // Custom callback
81
+ fetchMock.activate({
82
+ onUnhandledRequest: (request, print) => {
83
+ if (request.url.includes('/health')) return; // ignore
84
+ print.error(); // block everything else
85
+ },
86
+ });
87
+ ```
88
+
89
+ > `enableNetConnect()` takes priority over `onUnhandledRequest` — allowed hosts always pass through.
90
+
45
91
  ## API Overview
46
92
 
93
+ ### `fetchMock` (singleton)
94
+
95
+ A pre-built `FetchMock` instance for standalone use. Import and call `activate()` — no setup needed.
96
+
47
97
  ### `new FetchMock(server?)`
48
98
 
49
- Creates a `FetchMock` instance. Optionally accepts an existing MSW `SetupServer`; creates one internally if omitted.
99
+ Creates a `FetchMock` instance. Pass an existing MSW `SetupServer` to share interceptors; omit to create one internally.
50
100
 
51
101
  ### Intercepting & Replying
52
102
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ import { FetchMock } from './mock-server';
1
2
  export { createFetchMock, FetchMock } from './mock-server';
2
- export type { InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, } from './mock-server';
3
+ /** Pre-built singleton for quick standalone use (Cloudflare migration compatible). */
4
+ export declare const fetchMock: FetchMock;
5
+ export type { ActivateOptions, OnUnhandledRequest, InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, } from './mock-server';
3
6
  export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
4
7
  export type { MockCallHistoryLogData, CallHistoryFilterCriteria } from './mock-call-history';
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3D,YAAY,EACV,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE3D,sFAAsF;AACtF,eAAO,MAAM,SAAS,WAAkB,CAAC;AACzC,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,5 @@
1
+ import { FetchMock } from './mock-server';
1
2
  export { createFetchMock, FetchMock } from './mock-server';
3
+ /** Pre-built singleton for quick standalone use (Cloudflare migration compatible). */
4
+ export const fetchMock = new FetchMock();
2
5
  export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
@@ -45,18 +45,33 @@ export interface PendingInterceptor {
45
45
  timesInvoked: number;
46
46
  persist: boolean;
47
47
  }
48
+ type PrintAPI = {
49
+ warning(): void;
50
+ error(): void;
51
+ };
52
+ type OnUnhandledRequestCallback = (request: Request, print: PrintAPI) => void;
53
+ export type OnUnhandledRequest = 'bypass' | 'warn' | 'error' | OnUnhandledRequestCallback;
54
+ export interface ActivateOptions {
55
+ onUnhandledRequest?: OnUnhandledRequest;
56
+ }
48
57
  export declare class FetchMock {
49
58
  private readonly _calls;
50
59
  private server;
51
60
  private readonly ownsServer;
52
61
  private interceptors;
53
62
  private netConnectAllowed;
63
+ private mswHandlers;
54
64
  get calls(): MockCallHistory;
55
65
  constructor(externalServer?: SetupServerLike);
56
- activate(): void;
66
+ activate(options?: ActivateOptions): void;
57
67
  disableNetConnect(): void;
58
68
  enableNetConnect(matcher?: string | RegExp | ((host: string) => boolean)): void;
59
69
  private isNetConnectAllowed;
70
+ /**
71
+ * Remove consumed MSW handlers so future requests to those URLs
72
+ * go through MSW's onUnhandledRequest instead of silently passing through.
73
+ */
74
+ private syncMswHandlers;
60
75
  getCallHistory(): MockCallHistory;
61
76
  clearCallHistory(): void;
62
77
  deactivate(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"mock-server.d.ts","sourceRoot":"","sources":["../src/mock-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mock-server.d.ts","sourceRoot":"","sources":["../src/mock-server.ts"],"names":[],"mappings":"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"}
@@ -99,6 +99,8 @@ export class FetchMock {
99
99
  ownsServer;
100
100
  interceptors = [];
101
101
  netConnectAllowed = false;
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ mswHandlers = new Map();
102
104
  get calls() {
103
105
  return this._calls;
104
106
  }
@@ -106,14 +108,29 @@ export class FetchMock {
106
108
  this.server = externalServer ?? null;
107
109
  this.ownsServer = !externalServer;
108
110
  }
109
- activate() {
111
+ activate(options) {
110
112
  if (this.ownsServer) {
113
+ const isPatched = Object.getOwnPropertySymbols(globalThis.fetch).some((s) => s.description === 'isPatchedModule');
114
+ if (isPatched) {
115
+ throw new Error('Another MSW server is already active. ' +
116
+ 'Pass your existing server to new FetchMock(server) instead.');
117
+ }
118
+ const mode = options?.onUnhandledRequest ?? 'error';
111
119
  this.server = setupServer();
112
120
  this.server.listen({
113
121
  onUnhandledRequest: (request, print) => {
114
122
  if (this.isNetConnectAllowed(request))
115
123
  return;
116
- print.error();
124
+ if (typeof mode === 'function') {
125
+ mode(request, print);
126
+ }
127
+ else if (mode === 'error') {
128
+ print.error();
129
+ }
130
+ else if (mode === 'warn') {
131
+ print.warning();
132
+ }
133
+ // 'bypass' → do nothing
117
134
  },
118
135
  });
119
136
  }
@@ -136,6 +153,18 @@ export class FetchMock {
136
153
  return this.netConnectAllowed.test(host);
137
154
  return this.netConnectAllowed(host);
138
155
  }
156
+ /**
157
+ * Remove consumed MSW handlers so future requests to those URLs
158
+ * go through MSW's onUnhandledRequest instead of silently passing through.
159
+ */
160
+ syncMswHandlers() {
161
+ if (!this.server || !this.ownsServer)
162
+ return;
163
+ const activeHandlers = [...this.mswHandlers.entries()]
164
+ .filter(([p]) => !p.consumed || p.persist)
165
+ .map(([, handler]) => handler);
166
+ this.server.resetHandlers(...activeHandlers);
167
+ }
139
168
  getCallHistory() {
140
169
  return this._calls;
141
170
  }
@@ -144,6 +173,7 @@ export class FetchMock {
144
173
  }
145
174
  deactivate() {
146
175
  this.interceptors = [];
176
+ this.mswHandlers.clear();
147
177
  this._calls.clear();
148
178
  if (this.ownsServer) {
149
179
  this.server?.close();
@@ -153,6 +183,7 @@ export class FetchMock {
153
183
  assertNoPendingInterceptors() {
154
184
  const unconsumed = this.interceptors.filter(isPending);
155
185
  this.interceptors = [];
186
+ this.mswHandlers.clear();
156
187
  this._calls.clear();
157
188
  if (this.ownsServer) {
158
189
  this.server?.resetHandlers();
@@ -202,6 +233,7 @@ export class FetchMock {
202
233
  pending.timesInvoked++;
203
234
  if (!pending.persist && pending.timesInvoked >= pending.times) {
204
235
  pending.consumed = true;
236
+ this.syncMswHandlers();
205
237
  }
206
238
  recordCall(this._calls, request, bodyText);
207
239
  return bodyText;
@@ -211,6 +243,7 @@ export class FetchMock {
211
243
  if (!this.server) {
212
244
  throw new Error('FetchMock server is not active. Call activate() before registering interceptors.');
213
245
  }
246
+ this.mswHandlers.set(pending, handler);
214
247
  this.server.use(handler);
215
248
  };
216
249
  const buildChain = (delayRef) => ({
package/docs/api.md CHANGED
@@ -1,8 +1,20 @@
1
1
  # API Reference
2
2
 
3
+ ## `fetchMock` (singleton)
4
+
5
+ A pre-built `FetchMock` instance for standalone use. No setup required — just import and call `activate()`.
6
+
7
+ ```typescript
8
+ import { fetchMock } from 'msw-fetch-mock';
9
+
10
+ beforeAll(() => fetchMock.activate({ onUnhandledRequest: 'error' }));
11
+ afterAll(() => fetchMock.deactivate());
12
+ afterEach(() => fetchMock.assertNoPendingInterceptors());
13
+ ```
14
+
3
15
  ## `new FetchMock(server?)`
4
16
 
5
- Creates a `FetchMock` instance.
17
+ Creates a `FetchMock` instance. Pass an existing MSW `SetupServer` to share interceptors; omit to create one internally.
6
18
 
7
19
  ```typescript
8
20
  import { FetchMock } from 'msw-fetch-mock';
@@ -29,11 +41,46 @@ const fetchMock = new FetchMock(server);
29
41
  ### Lifecycle
30
42
 
31
43
  ```typescript
32
- fetchMock.activate(); // start intercepting (calls server.listen())
33
- fetchMock.deactivate(); // stop intercepting (calls server.close())
44
+ fetchMock.activate(options?); // start intercepting (calls server.listen())
45
+ fetchMock.deactivate(); // stop intercepting (calls server.close())
34
46
  ```
35
47
 
36
48
  > If you pass an external server that you manage yourself, `activate()` / `deactivate()` are no-ops.
49
+ >
50
+ > **Conflict detection:** In standalone mode, `activate()` checks whether `globalThis.fetch` is already patched by MSW. If so, it throws an error guiding you to pass your existing server via `new FetchMock(server)` instead.
51
+
52
+ #### `ActivateOptions`
53
+
54
+ | Property | Type | Default | Description |
55
+ | -------------------- | -------------------- | --------- | --------------------------------------------------- |
56
+ | `onUnhandledRequest` | `OnUnhandledRequest` | `'error'` | How to handle requests with no matching interceptor |
57
+
58
+ #### `OnUnhandledRequest`
59
+
60
+ | Value | Behavior |
61
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
62
+ | `'error'` | MSW prints an error and `fetch()` rejects with an `InternalError` |
63
+ | `'warn'` | MSW prints a warning; the request passes through to the real network |
64
+ | `'bypass'` | Silently passes through to the real network |
65
+ | `(request, print) => void` | Custom callback. Call `print.error()` to block or `print.warning()` to warn. Return without calling either to silently bypass. |
66
+
67
+ ```typescript
68
+ // Default — reject unmatched requests
69
+ fetchMock.activate();
70
+ fetchMock.activate({ onUnhandledRequest: 'error' });
71
+
72
+ // Custom callback
73
+ fetchMock.activate({
74
+ onUnhandledRequest: (request, print) => {
75
+ if (new URL(request.url).pathname === '/health') return;
76
+ print.error();
77
+ },
78
+ });
79
+ ```
80
+
81
+ > **Consumed interceptors:** Once a one-shot interceptor has been fully consumed, its MSW handler is removed. Subsequent requests to that URL are treated as unhandled and go through `onUnhandledRequest`. This prevents consumed interceptors from silently passing through.
82
+ >
83
+ > **Priority:** `enableNetConnect()` takes priority over `onUnhandledRequest` — allowed hosts always pass through regardless of the unhandled request mode.
37
84
 
38
85
  ### `fetchMock.calls`
39
86
 
@@ -6,7 +6,7 @@ If you're migrating tests from Cloudflare Workers' `cloudflare:test` to a standa
6
6
 
7
7
  | cloudflare:test | msw-fetch-mock |
8
8
  | -------------------------------------------------- | ----------------------------------------------------------- |
9
- | `import { fetchMock } from 'cloudflare:test'` | `const fetchMock = new FetchMock(server)` |
9
+ | `import { fetchMock } from 'cloudflare:test'` | `import { fetchMock } from 'msw-fetch-mock'` |
10
10
  | `fetchMock.activate()` | `fetchMock.activate()` |
11
11
  | `fetchMock.disableNetConnect()` | `fetchMock.disableNetConnect()` |
12
12
  | `fetchMock.enableNetConnect(matcher?)` | `fetchMock.enableNetConnect(matcher?)` |
@@ -44,11 +44,7 @@ it('calls API', async () => {
44
44
  ## After (msw-fetch-mock)
45
45
 
46
46
  ```typescript
47
- import { setupServer } from 'msw/node';
48
- import { FetchMock } from 'msw-fetch-mock';
49
-
50
- const server = setupServer();
51
- const fetchMock = new FetchMock(server);
47
+ import { fetchMock } from 'msw-fetch-mock';
52
48
 
53
49
  beforeAll(() => fetchMock.activate());
54
50
  afterAll(() => fetchMock.deactivate());
@@ -67,12 +63,13 @@ it('calls API', async () => {
67
63
 
68
64
  ## Key Differences
69
65
 
70
- | Aspect | cloudflare:test | msw-fetch-mock |
71
- | -------------------- | ------------------------------------ | ------------------------------------------------- |
72
- | Server lifecycle | Implicit (managed by test framework) | Explicit (`activate()` / `deactivate()`) |
73
- | Call history access | `fetchMock.getCallHistory()` | `fetchMock.getCallHistory()` or `fetchMock.calls` |
74
- | Call history cleanup | Automatic per test | Automatic via `assertNoPendingInterceptors()` |
75
- | Network connect | Must call `disableNetConnect()` | MSW blocks unhandled requests by default |
76
- | Runtime | Cloudflare Workers (workerd) | Node.js |
66
+ | Aspect | cloudflare:test | msw-fetch-mock |
67
+ | -------------------- | --------------------------------------------- | -------------------------------------------------- |
68
+ | Import | `import { fetchMock } from 'cloudflare:test'` | `import { fetchMock } from 'msw-fetch-mock'` |
69
+ | Server lifecycle | Implicit (managed by test framework) | Explicit (`activate()` / `deactivate()`) |
70
+ | Call history access | `fetchMock.getCallHistory()` | `fetchMock.getCallHistory()` or `fetchMock.calls` |
71
+ | Call history cleanup | Automatic per test | Automatic via `assertNoPendingInterceptors()` |
72
+ | Unhandled requests | Must call `disableNetConnect()` | `onUnhandledRequest: 'error'` by default (rejects) |
73
+ | Runtime | Cloudflare Workers (workerd) | Node.js |
77
74
 
78
75
  > **Note:** `getCallHistory()` and `clearCallHistory()` are provided as Cloudflare-compatible aliases. You can use either the Cloudflare-style methods or the `fetchMock.calls` getter — they are equivalent.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msw-fetch-mock",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Undici-style fetch mock API built on MSW (Mock Service Worker)",
5
5
  "type": "module",
6
6
  "license": "MIT",