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 +57 -7
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/mock-server.d.ts +16 -1
- package/dist/mock-server.d.ts.map +1 -1
- package/dist/mock-server.js +35 -2
- package/docs/api.md +50 -3
- package/docs/cloudflare-migration.md +10 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,14 +18,12 @@ npm install -D msw-fetch-mock msw
|
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
import { setupServer } from 'msw/node';
|
|
23
|
-
import { FetchMock } from 'msw-fetch-mock';
|
|
21
|
+
### Standalone (Cloudflare migration)
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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';
|
package/dist/mock-server.d.ts
CHANGED
|
@@ -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":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,2FAA2F;AAC3F,UAAU,eAAe;IACvB,GAAG,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IACvC,aAAa,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IACjD,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAChD,KAAK,IAAI,IAAI,CAAC;CACf;AAED,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC9D,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;AACjE,KAAK,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;AACzE,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;AAEjE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,KAAK,aAAa,GAAG,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAElF,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,IAAI,IAAI,CAAC;IAChB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,cAAc,CAAC;IAC9E,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,cAAc,CAAC;IAC/D,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,cAAc,CAAC;CAC9C;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAAC;CACvD;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AAiGD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,iBAAiB,CAA4B;IAErD,IAAI,KAAK,IAAI,eAAe,CAE3B;gBAEW,cAAc,CAAC,EAAE,eAAe;IAK5C,QAAQ,
|
|
1
|
+
{"version":3,"file":"mock-server.d.ts","sourceRoot":"","sources":["../src/mock-server.ts"],"names":[],"mappings":"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"}
|
package/dist/mock-server.js
CHANGED
|
@@ -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
|
-
|
|
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();
|
|
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'` | `
|
|
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 {
|
|
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
|
|
71
|
-
| -------------------- |
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
| Call history
|
|
75
|
-
|
|
|
76
|
-
|
|
|
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.
|