msw-fetch-mock 0.1.2 → 0.2.1
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 +33 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/mock-server.d.ts +17 -1
- package/dist/mock-server.d.ts.map +1 -1
- package/dist/mock-server.js +34 -4
- package/docs/api.md +55 -7
- package/docs/cloudflare-migration.md +12 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,9 +23,12 @@ npm install -D msw-fetch-mock msw
|
|
|
23
23
|
```typescript
|
|
24
24
|
import { fetchMock } from 'msw-fetch-mock';
|
|
25
25
|
|
|
26
|
-
beforeAll(() => fetchMock.activate());
|
|
26
|
+
beforeAll(() => fetchMock.activate({ onUnhandledRequest: 'error' }));
|
|
27
27
|
afterAll(() => fetchMock.deactivate());
|
|
28
|
-
afterEach(() =>
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
fetchMock.assertNoPendingInterceptors();
|
|
30
|
+
fetchMock.reset();
|
|
31
|
+
});
|
|
29
32
|
|
|
30
33
|
it('mocks a GET request', async () => {
|
|
31
34
|
fetchMock
|
|
@@ -62,6 +65,32 @@ afterEach(() => {
|
|
|
62
65
|
|
|
63
66
|
> **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
67
|
|
|
68
|
+
## Unhandled Requests
|
|
69
|
+
|
|
70
|
+
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).
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Reject unmatched requests (default)
|
|
74
|
+
fetchMock.activate();
|
|
75
|
+
fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
76
|
+
|
|
77
|
+
// Log a warning but allow passthrough
|
|
78
|
+
fetchMock.activate({ onUnhandledRequest: 'warn' });
|
|
79
|
+
|
|
80
|
+
// Silently allow passthrough
|
|
81
|
+
fetchMock.activate({ onUnhandledRequest: 'bypass' });
|
|
82
|
+
|
|
83
|
+
// Custom callback
|
|
84
|
+
fetchMock.activate({
|
|
85
|
+
onUnhandledRequest: (request, print) => {
|
|
86
|
+
if (request.url.includes('/health')) return; // ignore
|
|
87
|
+
print.error(); // block everything else
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> `enableNetConnect()` takes priority over `onUnhandledRequest` — allowed hosts always pass through.
|
|
93
|
+
|
|
65
94
|
## API Overview
|
|
66
95
|
|
|
67
96
|
### `fetchMock` (singleton)
|
|
@@ -91,10 +120,11 @@ fetchMock.calls.nthCall(2); // 2nd call (1-indexed)
|
|
|
91
120
|
fetchMock.calls.filterCalls({ method: 'POST', path: '/users' }, { operator: 'AND' });
|
|
92
121
|
```
|
|
93
122
|
|
|
94
|
-
### Assertions
|
|
123
|
+
### Assertions & Cleanup
|
|
95
124
|
|
|
96
125
|
```typescript
|
|
97
126
|
fetchMock.assertNoPendingInterceptors(); // throws if unconsumed interceptors remain
|
|
127
|
+
fetchMock.reset(); // clears interceptors + call history + handlers
|
|
98
128
|
```
|
|
99
129
|
|
|
100
130
|
## Documentation
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { FetchMock } from './mock-server';
|
|
|
2
2
|
export { createFetchMock, FetchMock } from './mock-server';
|
|
3
3
|
/** Pre-built singleton for quick standalone use (Cloudflare migration compatible). */
|
|
4
4
|
export declare const fetchMock: FetchMock;
|
|
5
|
-
export type { InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, } from './mock-server';
|
|
5
|
+
export type { ActivateOptions, OnUnhandledRequest, InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, } from './mock-server';
|
|
6
6
|
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
|
7
7
|
export type { MockCallHistoryLogData, CallHistoryFilterCriteria } from './mock-call-history';
|
|
8
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,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,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/mock-server.d.ts
CHANGED
|
@@ -45,21 +45,37 @@ 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;
|
|
78
|
+
reset(): void;
|
|
63
79
|
assertNoPendingInterceptors(): void;
|
|
64
80
|
pendingInterceptors(): PendingInterceptor[];
|
|
65
81
|
get(origin: string): MockPool;
|
|
@@ -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,19 +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) {
|
|
111
113
|
const isPatched = Object.getOwnPropertySymbols(globalThis.fetch).some((s) => s.description === 'isPatchedModule');
|
|
112
114
|
if (isPatched) {
|
|
113
115
|
throw new Error('Another MSW server is already active. ' +
|
|
114
116
|
'Pass your existing server to new FetchMock(server) instead.');
|
|
115
117
|
}
|
|
118
|
+
const mode = options?.onUnhandledRequest ?? 'error';
|
|
116
119
|
this.server = setupServer();
|
|
117
120
|
this.server.listen({
|
|
118
121
|
onUnhandledRequest: (request, print) => {
|
|
119
122
|
if (this.isNetConnectAllowed(request))
|
|
120
123
|
return;
|
|
121
|
-
|
|
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
|
|
122
134
|
},
|
|
123
135
|
});
|
|
124
136
|
}
|
|
@@ -141,6 +153,18 @@ export class FetchMock {
|
|
|
141
153
|
return this.netConnectAllowed.test(host);
|
|
142
154
|
return this.netConnectAllowed(host);
|
|
143
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
|
+
}
|
|
144
168
|
getCallHistory() {
|
|
145
169
|
return this._calls;
|
|
146
170
|
}
|
|
@@ -149,19 +173,23 @@ export class FetchMock {
|
|
|
149
173
|
}
|
|
150
174
|
deactivate() {
|
|
151
175
|
this.interceptors = [];
|
|
176
|
+
this.mswHandlers.clear();
|
|
152
177
|
this._calls.clear();
|
|
153
178
|
if (this.ownsServer) {
|
|
154
179
|
this.server?.close();
|
|
155
180
|
this.server = null;
|
|
156
181
|
}
|
|
157
182
|
}
|
|
158
|
-
|
|
159
|
-
const unconsumed = this.interceptors.filter(isPending);
|
|
183
|
+
reset() {
|
|
160
184
|
this.interceptors = [];
|
|
185
|
+
this.mswHandlers.clear();
|
|
161
186
|
this._calls.clear();
|
|
162
187
|
if (this.ownsServer) {
|
|
163
188
|
this.server?.resetHandlers();
|
|
164
189
|
}
|
|
190
|
+
}
|
|
191
|
+
assertNoPendingInterceptors() {
|
|
192
|
+
const unconsumed = this.interceptors.filter(isPending);
|
|
165
193
|
if (unconsumed.length > 0) {
|
|
166
194
|
const descriptions = unconsumed.map((p) => ` ${p.method} ${p.origin}${p.path}`);
|
|
167
195
|
throw new Error(`Pending interceptor(s) not consumed:\n${descriptions.join('\n')}`);
|
|
@@ -207,6 +235,7 @@ export class FetchMock {
|
|
|
207
235
|
pending.timesInvoked++;
|
|
208
236
|
if (!pending.persist && pending.timesInvoked >= pending.times) {
|
|
209
237
|
pending.consumed = true;
|
|
238
|
+
this.syncMswHandlers();
|
|
210
239
|
}
|
|
211
240
|
recordCall(this._calls, request, bodyText);
|
|
212
241
|
return bodyText;
|
|
@@ -216,6 +245,7 @@ export class FetchMock {
|
|
|
216
245
|
if (!this.server) {
|
|
217
246
|
throw new Error('FetchMock server is not active. Call activate() before registering interceptors.');
|
|
218
247
|
}
|
|
248
|
+
this.mswHandlers.set(pending, handler);
|
|
219
249
|
this.server.use(handler);
|
|
220
250
|
};
|
|
221
251
|
const buildChain = (delayRef) => ({
|
package/docs/api.md
CHANGED
|
@@ -7,9 +7,12 @@ A pre-built `FetchMock` instance for standalone use. No setup required — just
|
|
|
7
7
|
```typescript
|
|
8
8
|
import { fetchMock } from 'msw-fetch-mock';
|
|
9
9
|
|
|
10
|
-
beforeAll(() => fetchMock.activate());
|
|
10
|
+
beforeAll(() => fetchMock.activate({ onUnhandledRequest: 'error' }));
|
|
11
11
|
afterAll(() => fetchMock.deactivate());
|
|
12
|
-
afterEach(() =>
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
fetchMock.assertNoPendingInterceptors();
|
|
14
|
+
fetchMock.reset();
|
|
15
|
+
});
|
|
13
16
|
```
|
|
14
17
|
|
|
15
18
|
## `new FetchMock(server?)`
|
|
@@ -41,14 +44,47 @@ const fetchMock = new FetchMock(server);
|
|
|
41
44
|
### Lifecycle
|
|
42
45
|
|
|
43
46
|
```typescript
|
|
44
|
-
fetchMock.activate(); // start intercepting (calls server.listen())
|
|
45
|
-
fetchMock.deactivate();
|
|
47
|
+
fetchMock.activate(options?); // start intercepting (calls server.listen())
|
|
48
|
+
fetchMock.deactivate(); // stop intercepting (calls server.close())
|
|
46
49
|
```
|
|
47
50
|
|
|
48
51
|
> If you pass an external server that you manage yourself, `activate()` / `deactivate()` are no-ops.
|
|
49
52
|
>
|
|
50
53
|
> **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
54
|
|
|
55
|
+
#### `ActivateOptions`
|
|
56
|
+
|
|
57
|
+
| Property | Type | Default | Description |
|
|
58
|
+
| -------------------- | -------------------- | --------- | --------------------------------------------------- |
|
|
59
|
+
| `onUnhandledRequest` | `OnUnhandledRequest` | `'error'` | How to handle requests with no matching interceptor |
|
|
60
|
+
|
|
61
|
+
#### `OnUnhandledRequest`
|
|
62
|
+
|
|
63
|
+
| Value | Behavior |
|
|
64
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
65
|
+
| `'error'` | MSW prints an error and `fetch()` rejects with an `InternalError` |
|
|
66
|
+
| `'warn'` | MSW prints a warning; the request passes through to the real network |
|
|
67
|
+
| `'bypass'` | Silently passes through to the real network |
|
|
68
|
+
| `(request, print) => void` | Custom callback. Call `print.error()` to block or `print.warning()` to warn. Return without calling either to silently bypass. |
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Default — reject unmatched requests
|
|
72
|
+
fetchMock.activate();
|
|
73
|
+
fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
74
|
+
|
|
75
|
+
// Custom callback
|
|
76
|
+
fetchMock.activate({
|
|
77
|
+
onUnhandledRequest: (request, print) => {
|
|
78
|
+
if (new URL(request.url).pathname === '/health') return;
|
|
79
|
+
print.error();
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> **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.
|
|
85
|
+
>
|
|
86
|
+
> **Priority:** `enableNetConnect()` takes priority over `onUnhandledRequest` — allowed hosts always pass through regardless of the unhandled request mode.
|
|
87
|
+
|
|
52
88
|
### `fetchMock.calls`
|
|
53
89
|
|
|
54
90
|
Returns the `MockCallHistory` instance for inspecting and managing recorded requests.
|
|
@@ -108,13 +144,25 @@ Clears all recorded calls. Cloudflare-compatible alias for `fetchMock.calls.clea
|
|
|
108
144
|
|
|
109
145
|
### `fetchMock.assertNoPendingInterceptors()`
|
|
110
146
|
|
|
111
|
-
Throws an error if any registered interceptor has not been consumed.
|
|
147
|
+
Throws an error if any registered interceptor has not been consumed. This is a **pure assertion** — it does not clear call history, interceptors, or handlers. Use `reset()` to clean up state.
|
|
112
148
|
|
|
113
149
|
```typescript
|
|
114
|
-
afterEach(() =>
|
|
150
|
+
afterEach(() => {
|
|
151
|
+
fetchMock.assertNoPendingInterceptors();
|
|
152
|
+
fetchMock.reset();
|
|
153
|
+
});
|
|
115
154
|
```
|
|
116
155
|
|
|
117
|
-
|
|
156
|
+
### `fetchMock.reset()`
|
|
157
|
+
|
|
158
|
+
Clears all interceptors, call history, and MSW handlers. Resets the instance to a clean state without stopping the server. Use in `afterEach` after asserting no pending interceptors.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
afterEach(() => {
|
|
162
|
+
fetchMock.assertNoPendingInterceptors();
|
|
163
|
+
fetchMock.reset();
|
|
164
|
+
});
|
|
165
|
+
```
|
|
118
166
|
|
|
119
167
|
### `fetchMock.pendingInterceptors()`
|
|
120
168
|
|
|
@@ -48,7 +48,10 @@ import { fetchMock } from 'msw-fetch-mock';
|
|
|
48
48
|
|
|
49
49
|
beforeAll(() => fetchMock.activate());
|
|
50
50
|
afterAll(() => fetchMock.deactivate());
|
|
51
|
-
afterEach(() =>
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
fetchMock.assertNoPendingInterceptors();
|
|
53
|
+
fetchMock.reset();
|
|
54
|
+
});
|
|
52
55
|
|
|
53
56
|
it('calls API', async () => {
|
|
54
57
|
fetchMock
|
|
@@ -63,13 +66,13 @@ it('calls API', async () => {
|
|
|
63
66
|
|
|
64
67
|
## Key Differences
|
|
65
68
|
|
|
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 |
|
|
72
|
-
|
|
|
73
|
-
| Runtime | Cloudflare Workers (workerd) | Node.js
|
|
69
|
+
| Aspect | cloudflare:test | msw-fetch-mock |
|
|
70
|
+
| -------------------- | --------------------------------------------- | -------------------------------------------------- |
|
|
71
|
+
| Import | `import { fetchMock } from 'cloudflare:test'` | `import { fetchMock } from 'msw-fetch-mock'` |
|
|
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 | `reset()` in `afterEach` |
|
|
75
|
+
| Unhandled requests | Must call `disableNetConnect()` | `onUnhandledRequest: 'error'` by default (rejects) |
|
|
76
|
+
| Runtime | Cloudflare Workers (workerd) | Node.js |
|
|
74
77
|
|
|
75
78
|
> **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.
|