msw-fetch-mock 0.2.0 → 0.3.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 +51 -7
- package/dist/browser-adapter.d.ts +10 -0
- package/dist/browser-adapter.d.ts.map +1 -0
- package/dist/browser-adapter.js +20 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +8 -0
- package/dist/fetch-mock.d.ts +31 -0
- package/dist/fetch-mock.d.ts.map +1 -0
- package/dist/{mock-server.js → fetch-mock.js} +96 -51
- package/dist/index.d.ts +2 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -5
- package/dist/node-adapter.d.ts +11 -0
- package/dist/node-adapter.d.ts.map +1 -0
- package/dist/node-adapter.js +34 -0
- package/dist/node.d.ts +11 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +12 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/docs/api.md +79 -24
- package/docs/cloudflare-migration.md +9 -4
- package/package.json +14 -2
- package/dist/mock-server.d.ts +0 -84
- package/dist/mock-server.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Undici-style fetch mock API built on [MSW](https://mswjs.io/) (Mock Service Work
|
|
|
8
8
|
|
|
9
9
|
If you're familiar with Cloudflare Workers' `fetchMock` (from `cloudflare:test`) or Node.js undici's `MockAgent`, you already know this API.
|
|
10
10
|
|
|
11
|
+
Supports both **Node.js** (`msw/node`) and **Browser** (`msw/browser`) environments via subpath exports.
|
|
12
|
+
|
|
11
13
|
## Install
|
|
12
14
|
|
|
13
15
|
```bash
|
|
@@ -18,14 +20,19 @@ npm install -D msw-fetch-mock msw
|
|
|
18
20
|
|
|
19
21
|
## Quick Start
|
|
20
22
|
|
|
21
|
-
###
|
|
23
|
+
### Node.js (Vitest, Jest)
|
|
22
24
|
|
|
23
25
|
```typescript
|
|
26
|
+
// Works with default import or explicit /node subpath
|
|
24
27
|
import { fetchMock } from 'msw-fetch-mock';
|
|
28
|
+
// import { fetchMock } from 'msw-fetch-mock/node';
|
|
25
29
|
|
|
26
30
|
beforeAll(() => fetchMock.activate({ onUnhandledRequest: 'error' }));
|
|
27
31
|
afterAll(() => fetchMock.deactivate());
|
|
28
|
-
afterEach(() =>
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
fetchMock.assertNoPendingInterceptors();
|
|
34
|
+
fetchMock.reset();
|
|
35
|
+
});
|
|
29
36
|
|
|
30
37
|
it('mocks a GET request', async () => {
|
|
31
38
|
fetchMock
|
|
@@ -40,6 +47,25 @@ it('mocks a GET request', async () => {
|
|
|
40
47
|
});
|
|
41
48
|
```
|
|
42
49
|
|
|
50
|
+
### Browser (Storybook, Vitest Browser Mode)
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { setupWorker } from 'msw/browser';
|
|
54
|
+
import { createFetchMock } from 'msw-fetch-mock/browser';
|
|
55
|
+
|
|
56
|
+
const worker = setupWorker();
|
|
57
|
+
const fetchMock = createFetchMock(worker);
|
|
58
|
+
|
|
59
|
+
beforeAll(async () => {
|
|
60
|
+
await fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
61
|
+
});
|
|
62
|
+
afterAll(() => fetchMock.deactivate());
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
fetchMock.assertNoPendingInterceptors();
|
|
65
|
+
fetchMock.reset();
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
43
69
|
### With an existing MSW server
|
|
44
70
|
|
|
45
71
|
If you already use MSW, pass your server to share a single interceptor:
|
|
@@ -48,9 +74,10 @@ If you already use MSW, pass your server to share a single interceptor:
|
|
|
48
74
|
import { setupServer } from 'msw/node';
|
|
49
75
|
import { http, HttpResponse } from 'msw';
|
|
50
76
|
import { FetchMock } from 'msw-fetch-mock';
|
|
77
|
+
import { NodeMswAdapter } from 'msw-fetch-mock/node';
|
|
51
78
|
|
|
52
79
|
const server = setupServer(http.get('/api/users', () => HttpResponse.json([{ id: 1 }])));
|
|
53
|
-
const fetchMock = new FetchMock(server);
|
|
80
|
+
const fetchMock = new FetchMock(new NodeMswAdapter(server));
|
|
54
81
|
|
|
55
82
|
beforeAll(() => server.listen());
|
|
56
83
|
afterAll(() => server.close());
|
|
@@ -60,7 +87,7 @@ afterEach(() => {
|
|
|
60
87
|
});
|
|
61
88
|
```
|
|
62
89
|
|
|
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.
|
|
90
|
+
> **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(new NodeMswAdapter(server))` instead.
|
|
64
91
|
|
|
65
92
|
## Unhandled Requests
|
|
66
93
|
|
|
@@ -90,13 +117,29 @@ fetchMock.activate({
|
|
|
90
117
|
|
|
91
118
|
## API Overview
|
|
92
119
|
|
|
120
|
+
### Import Paths
|
|
121
|
+
|
|
122
|
+
| Path | Environment | MSW dependency |
|
|
123
|
+
| ------------------------ | ---------------------------- | -------------- |
|
|
124
|
+
| `msw-fetch-mock` | Node.js (re-exports `/node`) | `msw/node` |
|
|
125
|
+
| `msw-fetch-mock/node` | Node.js | `msw/node` |
|
|
126
|
+
| `msw-fetch-mock/browser` | Browser | `msw/browser` |
|
|
127
|
+
|
|
93
128
|
### `fetchMock` (singleton)
|
|
94
129
|
|
|
95
130
|
A pre-built `FetchMock` instance for standalone use. Import and call `activate()` — no setup needed.
|
|
131
|
+
Available from `msw-fetch-mock` and `msw-fetch-mock/node`.
|
|
132
|
+
|
|
133
|
+
### `createFetchMock(server?)` / `createFetchMock(worker)`
|
|
134
|
+
|
|
135
|
+
Factory function that creates a `FetchMock` with the appropriate adapter.
|
|
136
|
+
|
|
137
|
+
- Node: `createFetchMock(server?)` — optionally pass an existing MSW `SetupServer`
|
|
138
|
+
- Browser: `createFetchMock(worker)` — pass an MSW `SetupWorker` (required)
|
|
96
139
|
|
|
97
|
-
### `new FetchMock(
|
|
140
|
+
### `new FetchMock(adapter?)`
|
|
98
141
|
|
|
99
|
-
Creates a `FetchMock` instance
|
|
142
|
+
Creates a `FetchMock` instance with an explicit `MswAdapter`. Use `NodeMswAdapter` or `BrowserMswAdapter`.
|
|
100
143
|
|
|
101
144
|
### Intercepting & Replying
|
|
102
145
|
|
|
@@ -117,10 +160,11 @@ fetchMock.calls.nthCall(2); // 2nd call (1-indexed)
|
|
|
117
160
|
fetchMock.calls.filterCalls({ method: 'POST', path: '/users' }, { operator: 'AND' });
|
|
118
161
|
```
|
|
119
162
|
|
|
120
|
-
### Assertions
|
|
163
|
+
### Assertions & Cleanup
|
|
121
164
|
|
|
122
165
|
```typescript
|
|
123
166
|
fetchMock.assertNoPendingInterceptors(); // throws if unconsumed interceptors remain
|
|
167
|
+
fetchMock.reset(); // clears interceptors + call history + handlers
|
|
124
168
|
```
|
|
125
169
|
|
|
126
170
|
## Documentation
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MswAdapter, ResolvedActivateOptions, SetupWorkerLike } from './types';
|
|
2
|
+
export declare class BrowserMswAdapter implements MswAdapter {
|
|
3
|
+
private readonly worker;
|
|
4
|
+
constructor(worker: SetupWorkerLike);
|
|
5
|
+
use(...handlers: Array<unknown>): void;
|
|
6
|
+
resetHandlers(...handlers: Array<unknown>): void;
|
|
7
|
+
activate(options: ResolvedActivateOptions): Promise<void>;
|
|
8
|
+
deactivate(): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=browser-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-adapter.d.ts","sourceRoot":"","sources":["../src/browser-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEpF,qBAAa,iBAAkB,YAAW,UAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;gBAE7B,MAAM,EAAE,eAAe;IAInC,GAAG,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAItC,aAAa,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAI1C,QAAQ,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/D,UAAU,IAAI,IAAI;CAGnB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class BrowserMswAdapter {
|
|
2
|
+
worker;
|
|
3
|
+
constructor(worker) {
|
|
4
|
+
this.worker = worker;
|
|
5
|
+
}
|
|
6
|
+
use(...handlers) {
|
|
7
|
+
this.worker.use(...handlers);
|
|
8
|
+
}
|
|
9
|
+
resetHandlers(...handlers) {
|
|
10
|
+
this.worker.resetHandlers(...handlers);
|
|
11
|
+
}
|
|
12
|
+
async activate(options) {
|
|
13
|
+
await this.worker.start({
|
|
14
|
+
onUnhandledRequest: options.onUnhandledRequest,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
deactivate() {
|
|
18
|
+
this.worker.stop();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FetchMock } from './fetch-mock';
|
|
2
|
+
import type { SetupWorkerLike } from './types';
|
|
3
|
+
export { FetchMock } from './fetch-mock';
|
|
4
|
+
export { BrowserMswAdapter } from './browser-adapter';
|
|
5
|
+
export declare function createFetchMock(worker: SetupWorkerLike): FetchMock;
|
|
6
|
+
export type { ActivateOptions, OnUnhandledRequest, InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, MswAdapter, SetupWorkerLike, } from './types';
|
|
7
|
+
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
|
8
|
+
export type { MockCallHistoryLogData, CallHistoryFilterCriteria } from './mock-call-history';
|
|
9
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAElE;AAED,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { FetchMock } from './fetch-mock';
|
|
2
|
+
import { BrowserMswAdapter } from './browser-adapter';
|
|
3
|
+
export { FetchMock } from './fetch-mock';
|
|
4
|
+
export { BrowserMswAdapter } from './browser-adapter';
|
|
5
|
+
export function createFetchMock(worker) {
|
|
6
|
+
return new FetchMock(new BrowserMswAdapter(worker));
|
|
7
|
+
}
|
|
8
|
+
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { MockCallHistory } from './mock-call-history';
|
|
2
|
+
import type { MockPool, PendingInterceptor, ActivateOptions, MswAdapter, SetupServerLike, SetupWorkerLike } from './types';
|
|
3
|
+
export type { InterceptOptions, ReplyOptions, MockReplyChain, MockInterceptor, MockPool, PendingInterceptor, OnUnhandledRequest, ActivateOptions, } from './types';
|
|
4
|
+
export declare class FetchMock {
|
|
5
|
+
/** @internal */
|
|
6
|
+
static _defaultAdapterFactory?: () => MswAdapter;
|
|
7
|
+
private readonly _calls;
|
|
8
|
+
private adapter;
|
|
9
|
+
private interceptors;
|
|
10
|
+
private netConnectAllowed;
|
|
11
|
+
private mswHandlers;
|
|
12
|
+
get calls(): MockCallHistory;
|
|
13
|
+
constructor(input?: SetupServerLike | SetupWorkerLike | MswAdapter);
|
|
14
|
+
activate(options?: ActivateOptions): Promise<void>;
|
|
15
|
+
disableNetConnect(): void;
|
|
16
|
+
enableNetConnect(matcher?: string | RegExp | ((host: string) => boolean)): void;
|
|
17
|
+
private isNetConnectAllowed;
|
|
18
|
+
/**
|
|
19
|
+
* Remove consumed MSW handlers so future requests to those URLs
|
|
20
|
+
* go through MSW's onUnhandledRequest instead of silently passing through.
|
|
21
|
+
*/
|
|
22
|
+
private syncMswHandlers;
|
|
23
|
+
getCallHistory(): MockCallHistory;
|
|
24
|
+
clearCallHistory(): void;
|
|
25
|
+
deactivate(): void;
|
|
26
|
+
reset(): void;
|
|
27
|
+
assertNoPendingInterceptors(): void;
|
|
28
|
+
pendingInterceptors(): PendingInterceptor[];
|
|
29
|
+
get(origin: string): MockPool;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=fetch-mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-mock.d.ts","sourceRoot":"","sources":["../src/fetch-mock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAUV,QAAQ,EACR,kBAAkB,EAElB,eAAe,EACf,UAAU,EAEV,eAAe,EACf,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAC;AA2KjB,qBAAa,SAAS;IACpB,gBAAgB;IAChB,MAAM,CAAC,sBAAsB,CAAC,EAAE,MAAM,UAAU,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,iBAAiB,CAA4B;IAErD,OAAO,CAAC,WAAW,CAA2C;IAE9D,IAAI,KAAK,IAAI,eAAe,CAE3B;gBAEW,KAAK,CAAC,EAAE,eAAe,GAAG,eAAe,GAAG,UAAU;IAI5D,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBxD,iBAAiB,IAAI,IAAI;IAIzB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,IAAI;IAI/E,OAAO,CAAC,mBAAmB;IAS3B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAOvB,cAAc,IAAI,eAAe;IAIjC,gBAAgB,IAAI,IAAI;IAIxB,UAAU,IAAI,IAAI;IAOlB,KAAK,IAAI,IAAI;IAOb,2BAA2B,IAAI,IAAI;IAQnC,mBAAmB,IAAI,kBAAkB,EAAE;IAI3C,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ;CAqH9B"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw';
|
|
2
|
-
import { setupServer } from 'msw/node';
|
|
3
2
|
import { MockCallHistory } from './mock-call-history';
|
|
4
3
|
function isPending(p) {
|
|
5
4
|
if (p.persist)
|
|
@@ -93,10 +92,76 @@ function buildResponse(status, responseBody, replyOptions) {
|
|
|
93
92
|
}
|
|
94
93
|
return HttpResponse.json(responseBody, { status, headers });
|
|
95
94
|
}
|
|
95
|
+
function isSetupServerLike(input) {
|
|
96
|
+
return (typeof input === 'object' &&
|
|
97
|
+
input !== null &&
|
|
98
|
+
'listen' in input &&
|
|
99
|
+
typeof input.listen === 'function' &&
|
|
100
|
+
'close' in input &&
|
|
101
|
+
typeof input.close === 'function');
|
|
102
|
+
}
|
|
103
|
+
function isSetupWorkerLike(input) {
|
|
104
|
+
return (typeof input === 'object' &&
|
|
105
|
+
input !== null &&
|
|
106
|
+
'start' in input &&
|
|
107
|
+
typeof input.start === 'function' &&
|
|
108
|
+
'stop' in input &&
|
|
109
|
+
typeof input.stop === 'function');
|
|
110
|
+
}
|
|
111
|
+
function isMswAdapter(input) {
|
|
112
|
+
return (typeof input === 'object' &&
|
|
113
|
+
input !== null &&
|
|
114
|
+
'activate' in input &&
|
|
115
|
+
typeof input.activate === 'function' &&
|
|
116
|
+
'deactivate' in input &&
|
|
117
|
+
typeof input.deactivate === 'function');
|
|
118
|
+
}
|
|
119
|
+
function createServerAdapter(server) {
|
|
120
|
+
return {
|
|
121
|
+
use: (...handlers) => server.use(...handlers),
|
|
122
|
+
resetHandlers: (...handlers) => server.resetHandlers(...handlers),
|
|
123
|
+
activate(options) {
|
|
124
|
+
server.listen({ onUnhandledRequest: options.onUnhandledRequest });
|
|
125
|
+
},
|
|
126
|
+
deactivate() {
|
|
127
|
+
server.close();
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function createWorkerAdapter(worker) {
|
|
132
|
+
return {
|
|
133
|
+
use: (...handlers) => worker.use(...handlers),
|
|
134
|
+
resetHandlers: (...handlers) => worker.resetHandlers(...handlers),
|
|
135
|
+
async activate(options) {
|
|
136
|
+
await worker.start({ onUnhandledRequest: options.onUnhandledRequest });
|
|
137
|
+
},
|
|
138
|
+
deactivate() {
|
|
139
|
+
worker.stop();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function resolveAdapter(input) {
|
|
144
|
+
if (!input) {
|
|
145
|
+
if (!FetchMock._defaultAdapterFactory) {
|
|
146
|
+
throw new Error('FetchMock requires a server, worker, or adapter argument. ' +
|
|
147
|
+
'Use createFetchMock() from msw-fetch-mock/node or msw-fetch-mock/browser, ' +
|
|
148
|
+
'or pass a setupServer/setupWorker instance directly.');
|
|
149
|
+
}
|
|
150
|
+
return FetchMock._defaultAdapterFactory();
|
|
151
|
+
}
|
|
152
|
+
if (isMswAdapter(input))
|
|
153
|
+
return input;
|
|
154
|
+
if (isSetupServerLike(input))
|
|
155
|
+
return createServerAdapter(input);
|
|
156
|
+
if (isSetupWorkerLike(input))
|
|
157
|
+
return createWorkerAdapter(input);
|
|
158
|
+
throw new Error('Invalid argument: expected a setupServer, setupWorker, or MswAdapter instance.');
|
|
159
|
+
}
|
|
96
160
|
export class FetchMock {
|
|
161
|
+
/** @internal */
|
|
162
|
+
static _defaultAdapterFactory;
|
|
97
163
|
_calls = new MockCallHistory();
|
|
98
|
-
|
|
99
|
-
ownsServer;
|
|
164
|
+
adapter;
|
|
100
165
|
interceptors = [];
|
|
101
166
|
netConnectAllowed = false;
|
|
102
167
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -104,36 +169,27 @@ export class FetchMock {
|
|
|
104
169
|
get calls() {
|
|
105
170
|
return this._calls;
|
|
106
171
|
}
|
|
107
|
-
constructor(
|
|
108
|
-
this.
|
|
109
|
-
this.ownsServer = !externalServer;
|
|
172
|
+
constructor(input) {
|
|
173
|
+
this.adapter = resolveAdapter(input);
|
|
110
174
|
}
|
|
111
|
-
activate(options) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
else if (mode === 'warn') {
|
|
131
|
-
print.warning();
|
|
132
|
-
}
|
|
133
|
-
// 'bypass' → do nothing
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
}
|
|
175
|
+
async activate(options) {
|
|
176
|
+
const mode = options?.onUnhandledRequest ?? 'error';
|
|
177
|
+
await this.adapter.activate({
|
|
178
|
+
onUnhandledRequest: (request, print) => {
|
|
179
|
+
if (this.isNetConnectAllowed(request))
|
|
180
|
+
return;
|
|
181
|
+
if (typeof mode === 'function') {
|
|
182
|
+
mode(request, print);
|
|
183
|
+
}
|
|
184
|
+
else if (mode === 'error') {
|
|
185
|
+
print.error();
|
|
186
|
+
}
|
|
187
|
+
else if (mode === 'warn') {
|
|
188
|
+
print.warning();
|
|
189
|
+
}
|
|
190
|
+
// 'bypass' → do nothing
|
|
191
|
+
},
|
|
192
|
+
});
|
|
137
193
|
}
|
|
138
194
|
disableNetConnect() {
|
|
139
195
|
this.netConnectAllowed = false;
|
|
@@ -158,12 +214,10 @@ export class FetchMock {
|
|
|
158
214
|
* go through MSW's onUnhandledRequest instead of silently passing through.
|
|
159
215
|
*/
|
|
160
216
|
syncMswHandlers() {
|
|
161
|
-
if (!this.server || !this.ownsServer)
|
|
162
|
-
return;
|
|
163
217
|
const activeHandlers = [...this.mswHandlers.entries()]
|
|
164
218
|
.filter(([p]) => !p.consumed || p.persist)
|
|
165
219
|
.map(([, handler]) => handler);
|
|
166
|
-
this.
|
|
220
|
+
this.adapter.resetHandlers(...activeHandlers);
|
|
167
221
|
}
|
|
168
222
|
getCallHistory() {
|
|
169
223
|
return this._calls;
|
|
@@ -175,19 +229,16 @@ export class FetchMock {
|
|
|
175
229
|
this.interceptors = [];
|
|
176
230
|
this.mswHandlers.clear();
|
|
177
231
|
this._calls.clear();
|
|
178
|
-
|
|
179
|
-
this.server?.close();
|
|
180
|
-
this.server = null;
|
|
181
|
-
}
|
|
232
|
+
this.adapter.deactivate();
|
|
182
233
|
}
|
|
183
|
-
|
|
184
|
-
const unconsumed = this.interceptors.filter(isPending);
|
|
234
|
+
reset() {
|
|
185
235
|
this.interceptors = [];
|
|
186
236
|
this.mswHandlers.clear();
|
|
187
237
|
this._calls.clear();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
238
|
+
this.adapter.resetHandlers();
|
|
239
|
+
}
|
|
240
|
+
assertNoPendingInterceptors() {
|
|
241
|
+
const unconsumed = this.interceptors.filter(isPending);
|
|
191
242
|
if (unconsumed.length > 0) {
|
|
192
243
|
const descriptions = unconsumed.map((p) => ` ${p.method} ${p.origin}${p.path}`);
|
|
193
244
|
throw new Error(`Pending interceptor(s) not consumed:\n${descriptions.join('\n')}`);
|
|
@@ -240,11 +291,8 @@ export class FetchMock {
|
|
|
240
291
|
};
|
|
241
292
|
const registerHandler = (handlerFn) => {
|
|
242
293
|
const handler = getHttpMethod(method)(urlPattern, async ({ request }) => handlerFn(request));
|
|
243
|
-
if (!this.server) {
|
|
244
|
-
throw new Error('FetchMock server is not active. Call activate() before registering interceptors.');
|
|
245
|
-
}
|
|
246
294
|
this.mswHandlers.set(pending, handler);
|
|
247
|
-
this.
|
|
295
|
+
this.adapter.use(handler);
|
|
248
296
|
};
|
|
249
297
|
const buildChain = (delayRef) => ({
|
|
250
298
|
times(n) {
|
|
@@ -297,6 +345,3 @@ export class FetchMock {
|
|
|
297
345
|
};
|
|
298
346
|
}
|
|
299
347
|
}
|
|
300
|
-
export function createFetchMock(externalServer) {
|
|
301
|
-
return new FetchMock(externalServer);
|
|
302
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
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';
|
|
6
|
-
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
|
7
|
-
export type { MockCallHistoryLogData, CallHistoryFilterCriteria } from './mock-call-history';
|
|
1
|
+
export { FetchMock, NodeMswAdapter, createFetchMock, fetchMock, MockCallHistory, MockCallHistoryLog, } from './node';
|
|
2
|
+
export type { ActivateOptions, OnUnhandledRequest, InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, MswAdapter, SetupServerLike, MockCallHistoryLogData, CallHistoryFilterCriteria, } from './node';
|
|
8
3
|
//# 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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,cAAc,EACd,eAAe,EACf,SAAS,EACT,eAAe,EACf,kBAAkB,GACnB,MAAM,QAAQ,CAAC;AAChB,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
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();
|
|
5
|
-
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
|
1
|
+
export { FetchMock, NodeMswAdapter, createFetchMock, fetchMock, MockCallHistory, MockCallHistoryLog, } from './node';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MswAdapter, ResolvedActivateOptions, SetupServerLike } from './types';
|
|
2
|
+
export declare class NodeMswAdapter implements MswAdapter {
|
|
3
|
+
private server;
|
|
4
|
+
private readonly ownsServer;
|
|
5
|
+
constructor(externalServer?: SetupServerLike);
|
|
6
|
+
use(...handlers: Array<unknown>): void;
|
|
7
|
+
resetHandlers(...handlers: Array<unknown>): void;
|
|
8
|
+
activate(options: ResolvedActivateOptions): void;
|
|
9
|
+
deactivate(): void;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=node-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEpF,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;gBAEzB,cAAc,CAAC,EAAE,eAAe;IAK5C,GAAG,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAItC,aAAa,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI;IAIhD,QAAQ,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI;IAmBhD,UAAU,IAAI,IAAI;CAMnB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { setupServer } from 'msw/node';
|
|
2
|
+
export class NodeMswAdapter {
|
|
3
|
+
server;
|
|
4
|
+
ownsServer;
|
|
5
|
+
constructor(externalServer) {
|
|
6
|
+
this.server = externalServer ?? null;
|
|
7
|
+
this.ownsServer = !externalServer;
|
|
8
|
+
}
|
|
9
|
+
use(...handlers) {
|
|
10
|
+
this.server.use(...handlers);
|
|
11
|
+
}
|
|
12
|
+
resetHandlers(...handlers) {
|
|
13
|
+
this.server.resetHandlers(...handlers);
|
|
14
|
+
}
|
|
15
|
+
activate(options) {
|
|
16
|
+
if (!this.ownsServer)
|
|
17
|
+
return;
|
|
18
|
+
const isPatched = Object.getOwnPropertySymbols(globalThis.fetch).some((s) => s.description === 'isPatchedModule');
|
|
19
|
+
if (isPatched) {
|
|
20
|
+
throw new Error('Another MSW server is already active. ' +
|
|
21
|
+
'Pass your existing server to new FetchMock(server) instead.');
|
|
22
|
+
}
|
|
23
|
+
this.server = setupServer();
|
|
24
|
+
this.server.listen({
|
|
25
|
+
onUnhandledRequest: options.onUnhandledRequest,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
deactivate() {
|
|
29
|
+
if (this.ownsServer) {
|
|
30
|
+
this.server?.close();
|
|
31
|
+
this.server = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FetchMock } from './fetch-mock';
|
|
2
|
+
import type { SetupServerLike } from './types';
|
|
3
|
+
export { FetchMock } from './fetch-mock';
|
|
4
|
+
export { NodeMswAdapter } from './node-adapter';
|
|
5
|
+
export declare function createFetchMock(server?: SetupServerLike): FetchMock;
|
|
6
|
+
/** Pre-built singleton for quick standalone use (Cloudflare migration compatible). */
|
|
7
|
+
export declare const fetchMock: FetchMock;
|
|
8
|
+
export type { ActivateOptions, OnUnhandledRequest, InterceptOptions, MockPool, MockInterceptor, MockReplyChain, ReplyOptions, PendingInterceptor, MswAdapter, SetupServerLike, } from './types';
|
|
9
|
+
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
|
10
|
+
export type { MockCallHistoryLogData, CallHistoryFilterCriteria } from './mock-call-history';
|
|
11
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKhD,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,SAAS,CAEnE;AAED,sFAAsF;AACtF,eAAO,MAAM,SAAS,WAAoB,CAAC;AAE3C,YAAY,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,QAAQ,EACR,eAAe,EACf,cAAc,EACd,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,eAAe,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,YAAY,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FetchMock } from './fetch-mock';
|
|
2
|
+
import { NodeMswAdapter } from './node-adapter';
|
|
3
|
+
export { FetchMock } from './fetch-mock';
|
|
4
|
+
export { NodeMswAdapter } from './node-adapter';
|
|
5
|
+
/** Register Node.js as the default adapter environment so `new FetchMock()` works. */
|
|
6
|
+
FetchMock._defaultAdapterFactory = () => new NodeMswAdapter();
|
|
7
|
+
export function createFetchMock(server) {
|
|
8
|
+
return new FetchMock(new NodeMswAdapter(server));
|
|
9
|
+
}
|
|
10
|
+
/** Pre-built singleton for quick standalone use (Cloudflare migration compatible). */
|
|
11
|
+
export const fetchMock = createFetchMock();
|
|
12
|
+
export { MockCallHistory, MockCallHistoryLog } from './mock-call-history';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
2
|
+
export type PathMatcher = string | RegExp | ((path: string) => boolean);
|
|
3
|
+
export type HeaderValueMatcher = string | RegExp | ((value: string) => boolean);
|
|
4
|
+
export type BodyMatcher = string | RegExp | ((body: string) => boolean);
|
|
5
|
+
export interface InterceptOptions {
|
|
6
|
+
path: PathMatcher;
|
|
7
|
+
method?: HttpMethod;
|
|
8
|
+
headers?: Record<string, HeaderValueMatcher>;
|
|
9
|
+
body?: BodyMatcher;
|
|
10
|
+
query?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export interface ReplyOptions {
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export type ReplyCallback = (req: {
|
|
16
|
+
body: string | null;
|
|
17
|
+
}) => unknown | Promise<unknown>;
|
|
18
|
+
export interface MockReplyChain {
|
|
19
|
+
times(n: number): void;
|
|
20
|
+
persist(): void;
|
|
21
|
+
delay(ms: number): void;
|
|
22
|
+
}
|
|
23
|
+
export interface MockInterceptor {
|
|
24
|
+
reply(status: number, body?: unknown, options?: ReplyOptions): MockReplyChain;
|
|
25
|
+
reply(status: number, callback: ReplyCallback): MockReplyChain;
|
|
26
|
+
replyWithError(error: Error): MockReplyChain;
|
|
27
|
+
}
|
|
28
|
+
export interface MockPool {
|
|
29
|
+
intercept(options: InterceptOptions): MockInterceptor;
|
|
30
|
+
}
|
|
31
|
+
export interface PendingInterceptor {
|
|
32
|
+
origin: string;
|
|
33
|
+
path: string;
|
|
34
|
+
method: string;
|
|
35
|
+
consumed: boolean;
|
|
36
|
+
times: number;
|
|
37
|
+
timesInvoked: number;
|
|
38
|
+
persist: boolean;
|
|
39
|
+
}
|
|
40
|
+
export type NetConnectMatcher = true | false | string | RegExp | ((host: string) => boolean);
|
|
41
|
+
export type PrintAPI = {
|
|
42
|
+
warning(): void;
|
|
43
|
+
error(): void;
|
|
44
|
+
};
|
|
45
|
+
export type OnUnhandledRequestCallback = (request: Request, print: PrintAPI) => void;
|
|
46
|
+
export type OnUnhandledRequest = 'bypass' | 'warn' | 'error' | OnUnhandledRequestCallback;
|
|
47
|
+
export interface ActivateOptions {
|
|
48
|
+
onUnhandledRequest?: OnUnhandledRequest;
|
|
49
|
+
}
|
|
50
|
+
export interface ResolvedActivateOptions {
|
|
51
|
+
onUnhandledRequest: OnUnhandledRequestCallback;
|
|
52
|
+
}
|
|
53
|
+
/** Structural type to avoid cross-package nominal type mismatch on MSW's private fields */
|
|
54
|
+
export interface SetupServerLike {
|
|
55
|
+
use(...handlers: Array<unknown>): void;
|
|
56
|
+
resetHandlers(...handlers: Array<unknown>): void;
|
|
57
|
+
listen(options?: Record<string, unknown>): void;
|
|
58
|
+
close(): void;
|
|
59
|
+
}
|
|
60
|
+
/** Structural type for MSW's setupWorker return type */
|
|
61
|
+
export interface SetupWorkerLike {
|
|
62
|
+
use(...handlers: Array<unknown>): void;
|
|
63
|
+
resetHandlers(...handlers: Array<unknown>): void;
|
|
64
|
+
start(options?: Record<string, unknown>): Promise<void>;
|
|
65
|
+
stop(): void;
|
|
66
|
+
}
|
|
67
|
+
/** Environment-agnostic adapter interface for MSW server/worker */
|
|
68
|
+
export interface MswAdapter {
|
|
69
|
+
use(...handlers: Array<unknown>): void;
|
|
70
|
+
resetHandlers(...handlers: Array<unknown>): void;
|
|
71
|
+
activate(options: ResolvedActivateOptions): void | Promise<void>;
|
|
72
|
+
deactivate(): void;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/docs/api.md
CHANGED
|
@@ -1,38 +1,81 @@
|
|
|
1
1
|
# API Reference
|
|
2
2
|
|
|
3
|
+
## Import Paths
|
|
4
|
+
|
|
5
|
+
| Path | Environment | MSW dependency |
|
|
6
|
+
| ------------------------ | ---------------------------- | -------------- |
|
|
7
|
+
| `msw-fetch-mock` | Node.js (re-exports `/node`) | `msw/node` |
|
|
8
|
+
| `msw-fetch-mock/node` | Node.js | `msw/node` |
|
|
9
|
+
| `msw-fetch-mock/browser` | Browser | `msw/browser` |
|
|
10
|
+
|
|
3
11
|
## `fetchMock` (singleton)
|
|
4
12
|
|
|
5
|
-
A pre-built `FetchMock` instance for standalone use. No setup required — just import and call `activate()`.
|
|
13
|
+
A pre-built `FetchMock` instance for standalone Node.js use. No setup required — just import and call `activate()`.
|
|
6
14
|
|
|
7
15
|
```typescript
|
|
8
16
|
import { fetchMock } from 'msw-fetch-mock';
|
|
9
17
|
|
|
10
|
-
beforeAll(() =>
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
await fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
20
|
+
});
|
|
11
21
|
afterAll(() => fetchMock.deactivate());
|
|
12
|
-
afterEach(() =>
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
fetchMock.assertNoPendingInterceptors();
|
|
24
|
+
fetchMock.reset();
|
|
25
|
+
});
|
|
13
26
|
```
|
|
14
27
|
|
|
15
|
-
## `
|
|
28
|
+
## `createFetchMock(server?)` (Node)
|
|
16
29
|
|
|
17
|
-
Creates a `FetchMock`
|
|
30
|
+
Creates a `FetchMock` with `NodeMswAdapter`. Optionally pass an existing MSW server.
|
|
18
31
|
|
|
19
32
|
```typescript
|
|
20
|
-
import {
|
|
33
|
+
import { createFetchMock } from 'msw-fetch-mock/node';
|
|
34
|
+
import { setupServer } from 'msw/node';
|
|
21
35
|
|
|
22
|
-
// Standalone
|
|
23
|
-
const fetchMock =
|
|
36
|
+
// Standalone
|
|
37
|
+
const fetchMock = createFetchMock();
|
|
24
38
|
|
|
25
39
|
// With external MSW server
|
|
26
|
-
import { setupServer } from 'msw/node';
|
|
27
40
|
const server = setupServer();
|
|
28
|
-
const fetchMock =
|
|
41
|
+
const fetchMock = createFetchMock(server);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `createFetchMock(worker)` (Browser)
|
|
45
|
+
|
|
46
|
+
Creates a `FetchMock` with `BrowserMswAdapter`. Requires an MSW worker.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { setupWorker } from 'msw/browser';
|
|
50
|
+
import { createFetchMock } from 'msw-fetch-mock/browser';
|
|
51
|
+
|
|
52
|
+
const worker = setupWorker();
|
|
53
|
+
const fetchMock = createFetchMock(worker);
|
|
54
|
+
|
|
55
|
+
beforeAll(async () => {
|
|
56
|
+
await fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
57
|
+
});
|
|
29
58
|
```
|
|
30
59
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
## `new FetchMock(adapter?)`
|
|
61
|
+
|
|
62
|
+
Creates a `FetchMock` instance with an explicit `MswAdapter`.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { FetchMock } from 'msw-fetch-mock';
|
|
66
|
+
import { NodeMswAdapter } from 'msw-fetch-mock/node';
|
|
67
|
+
import { BrowserMswAdapter } from 'msw-fetch-mock/browser';
|
|
68
|
+
|
|
69
|
+
// Node with external server
|
|
70
|
+
const fetchMock = new FetchMock(new NodeMswAdapter(server));
|
|
71
|
+
|
|
72
|
+
// Browser with worker
|
|
73
|
+
const fetchMock = new FetchMock(new BrowserMswAdapter(worker));
|
|
74
|
+
```
|
|
34
75
|
|
|
35
|
-
|
|
76
|
+
| Parameter | Type | Required | Description |
|
|
77
|
+
| --------- | ------------ | -------- | ----------------------------------------------------- |
|
|
78
|
+
| `adapter` | `MswAdapter` | No | Environment adapter. Use `createFetchMock()` instead. |
|
|
36
79
|
|
|
37
80
|
---
|
|
38
81
|
|
|
@@ -41,13 +84,13 @@ const fetchMock = new FetchMock(server);
|
|
|
41
84
|
### Lifecycle
|
|
42
85
|
|
|
43
86
|
```typescript
|
|
44
|
-
fetchMock.activate(options?); // start intercepting (
|
|
45
|
-
fetchMock.deactivate();
|
|
87
|
+
await fetchMock.activate(options?); // start intercepting (async — browser needs worker.start())
|
|
88
|
+
fetchMock.deactivate(); // stop intercepting
|
|
46
89
|
```
|
|
47
90
|
|
|
48
|
-
>
|
|
91
|
+
> `activate()` returns `Promise<void>`. In Node.js, the promise resolves synchronously. In the browser, it waits for the Service Worker to start. Vitest and Jest handle async `beforeAll` natively.
|
|
49
92
|
>
|
|
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
|
|
93
|
+
> **Conflict detection (Node only):** In standalone mode, `activate()` checks whether `globalThis.fetch` is already patched by MSW. If so, it throws an error guiding you to use `new FetchMock(new NodeMswAdapter(server))` instead.
|
|
51
94
|
|
|
52
95
|
#### `ActivateOptions`
|
|
53
96
|
|
|
@@ -66,11 +109,11 @@ fetchMock.deactivate(); // stop intercepting (calls server.close())
|
|
|
66
109
|
|
|
67
110
|
```typescript
|
|
68
111
|
// Default — reject unmatched requests
|
|
69
|
-
fetchMock.activate();
|
|
70
|
-
fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
112
|
+
await fetchMock.activate();
|
|
113
|
+
await fetchMock.activate({ onUnhandledRequest: 'error' });
|
|
71
114
|
|
|
72
115
|
// Custom callback
|
|
73
|
-
fetchMock.activate({
|
|
116
|
+
await fetchMock.activate({
|
|
74
117
|
onUnhandledRequest: (request, print) => {
|
|
75
118
|
if (new URL(request.url).pathname === '/health') return;
|
|
76
119
|
print.error();
|
|
@@ -141,13 +184,25 @@ Clears all recorded calls. Cloudflare-compatible alias for `fetchMock.calls.clea
|
|
|
141
184
|
|
|
142
185
|
### `fetchMock.assertNoPendingInterceptors()`
|
|
143
186
|
|
|
144
|
-
Throws an error if any registered interceptor has not been consumed.
|
|
187
|
+
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.
|
|
145
188
|
|
|
146
189
|
```typescript
|
|
147
|
-
afterEach(() =>
|
|
190
|
+
afterEach(() => {
|
|
191
|
+
fetchMock.assertNoPendingInterceptors();
|
|
192
|
+
fetchMock.reset();
|
|
193
|
+
});
|
|
148
194
|
```
|
|
149
195
|
|
|
150
|
-
|
|
196
|
+
### `fetchMock.reset()`
|
|
197
|
+
|
|
198
|
+
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.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
afterEach(() => {
|
|
202
|
+
fetchMock.assertNoPendingInterceptors();
|
|
203
|
+
fetchMock.reset();
|
|
204
|
+
});
|
|
205
|
+
```
|
|
151
206
|
|
|
152
207
|
### `fetchMock.pendingInterceptors()`
|
|
153
208
|
|
|
@@ -7,7 +7,7 @@ If you're migrating tests from Cloudflare Workers' `cloudflare:test` to a standa
|
|
|
7
7
|
| cloudflare:test | msw-fetch-mock |
|
|
8
8
|
| -------------------------------------------------- | ----------------------------------------------------------- |
|
|
9
9
|
| `import { fetchMock } from 'cloudflare:test'` | `import { fetchMock } from 'msw-fetch-mock'` |
|
|
10
|
-
| `fetchMock.activate()` | `fetchMock.activate()`
|
|
10
|
+
| `fetchMock.activate()` | `await fetchMock.activate()` |
|
|
11
11
|
| `fetchMock.disableNetConnect()` | `fetchMock.disableNetConnect()` |
|
|
12
12
|
| `fetchMock.enableNetConnect(matcher?)` | `fetchMock.enableNetConnect(matcher?)` |
|
|
13
13
|
| `fetchMock.deactivate()` | `fetchMock.deactivate()` |
|
|
@@ -46,9 +46,14 @@ it('calls API', async () => {
|
|
|
46
46
|
```typescript
|
|
47
47
|
import { fetchMock } from 'msw-fetch-mock';
|
|
48
48
|
|
|
49
|
-
beforeAll(() =>
|
|
49
|
+
beforeAll(async () => {
|
|
50
|
+
await fetchMock.activate();
|
|
51
|
+
});
|
|
50
52
|
afterAll(() => fetchMock.deactivate());
|
|
51
|
-
afterEach(() =>
|
|
53
|
+
afterEach(() => {
|
|
54
|
+
fetchMock.assertNoPendingInterceptors();
|
|
55
|
+
fetchMock.reset();
|
|
56
|
+
});
|
|
52
57
|
|
|
53
58
|
it('calls API', async () => {
|
|
54
59
|
fetchMock
|
|
@@ -68,7 +73,7 @@ it('calls API', async () => {
|
|
|
68
73
|
| Import | `import { fetchMock } from 'cloudflare:test'` | `import { fetchMock } from 'msw-fetch-mock'` |
|
|
69
74
|
| Server lifecycle | Implicit (managed by test framework) | Explicit (`activate()` / `deactivate()`) |
|
|
70
75
|
| Call history access | `fetchMock.getCallHistory()` | `fetchMock.getCallHistory()` or `fetchMock.calls` |
|
|
71
|
-
| Call history cleanup | Automatic per test |
|
|
76
|
+
| Call history cleanup | Automatic per test | `reset()` in `afterEach` |
|
|
72
77
|
| Unhandled requests | Must call `disableNetConnect()` | `onUnhandledRequest: 'error'` by default (rejects) |
|
|
73
78
|
| Runtime | Cloudflare Workers (workerd) | Node.js |
|
|
74
79
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "msw-fetch-mock",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Undici-style fetch mock API built on MSW (Mock Service Worker)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"fetch",
|
|
10
10
|
"mock",
|
|
11
11
|
"testing",
|
|
12
|
-
"undici"
|
|
12
|
+
"undici",
|
|
13
|
+
"browser",
|
|
14
|
+
"service-worker"
|
|
13
15
|
],
|
|
14
16
|
"repository": {
|
|
15
17
|
"type": "git",
|
|
@@ -24,6 +26,16 @@
|
|
|
24
26
|
"source": "./src/index.ts",
|
|
25
27
|
"types": "./dist/index.d.ts",
|
|
26
28
|
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./node": {
|
|
31
|
+
"source": "./src/node.ts",
|
|
32
|
+
"types": "./dist/node.d.ts",
|
|
33
|
+
"import": "./dist/node.js"
|
|
34
|
+
},
|
|
35
|
+
"./browser": {
|
|
36
|
+
"source": "./src/browser.ts",
|
|
37
|
+
"types": "./dist/browser.d.ts",
|
|
38
|
+
"import": "./dist/browser.js"
|
|
27
39
|
}
|
|
28
40
|
},
|
|
29
41
|
"files": [
|
package/dist/mock-server.d.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { MockCallHistory } from './mock-call-history';
|
|
2
|
-
/** Structural type to avoid cross-package nominal type mismatch on MSW's private fields */
|
|
3
|
-
interface SetupServerLike {
|
|
4
|
-
use(...handlers: Array<unknown>): void;
|
|
5
|
-
resetHandlers(...handlers: Array<unknown>): void;
|
|
6
|
-
listen(options?: Record<string, unknown>): void;
|
|
7
|
-
close(): void;
|
|
8
|
-
}
|
|
9
|
-
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
10
|
-
type PathMatcher = string | RegExp | ((path: string) => boolean);
|
|
11
|
-
type HeaderValueMatcher = string | RegExp | ((value: string) => boolean);
|
|
12
|
-
type BodyMatcher = string | RegExp | ((body: string) => boolean);
|
|
13
|
-
export interface InterceptOptions {
|
|
14
|
-
path: PathMatcher;
|
|
15
|
-
method?: HttpMethod;
|
|
16
|
-
headers?: Record<string, HeaderValueMatcher>;
|
|
17
|
-
body?: BodyMatcher;
|
|
18
|
-
query?: Record<string, string>;
|
|
19
|
-
}
|
|
20
|
-
export interface ReplyOptions {
|
|
21
|
-
headers?: Record<string, string>;
|
|
22
|
-
}
|
|
23
|
-
type ReplyCallback = (req: {
|
|
24
|
-
body: string | null;
|
|
25
|
-
}) => unknown | Promise<unknown>;
|
|
26
|
-
export interface MockReplyChain {
|
|
27
|
-
times(n: number): void;
|
|
28
|
-
persist(): void;
|
|
29
|
-
delay(ms: number): void;
|
|
30
|
-
}
|
|
31
|
-
export interface MockInterceptor {
|
|
32
|
-
reply(status: number, body?: unknown, options?: ReplyOptions): MockReplyChain;
|
|
33
|
-
reply(status: number, callback: ReplyCallback): MockReplyChain;
|
|
34
|
-
replyWithError(error: Error): MockReplyChain;
|
|
35
|
-
}
|
|
36
|
-
export interface MockPool {
|
|
37
|
-
intercept(options: InterceptOptions): MockInterceptor;
|
|
38
|
-
}
|
|
39
|
-
export interface PendingInterceptor {
|
|
40
|
-
origin: string;
|
|
41
|
-
path: string;
|
|
42
|
-
method: string;
|
|
43
|
-
consumed: boolean;
|
|
44
|
-
times: number;
|
|
45
|
-
timesInvoked: number;
|
|
46
|
-
persist: boolean;
|
|
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
|
-
}
|
|
57
|
-
export declare class FetchMock {
|
|
58
|
-
private readonly _calls;
|
|
59
|
-
private server;
|
|
60
|
-
private readonly ownsServer;
|
|
61
|
-
private interceptors;
|
|
62
|
-
private netConnectAllowed;
|
|
63
|
-
private mswHandlers;
|
|
64
|
-
get calls(): MockCallHistory;
|
|
65
|
-
constructor(externalServer?: SetupServerLike);
|
|
66
|
-
activate(options?: ActivateOptions): void;
|
|
67
|
-
disableNetConnect(): void;
|
|
68
|
-
enableNetConnect(matcher?: string | RegExp | ((host: string) => boolean)): void;
|
|
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;
|
|
75
|
-
getCallHistory(): MockCallHistory;
|
|
76
|
-
clearCallHistory(): void;
|
|
77
|
-
deactivate(): void;
|
|
78
|
-
assertNoPendingInterceptors(): void;
|
|
79
|
-
pendingInterceptors(): PendingInterceptor[];
|
|
80
|
-
get(origin: string): MockPool;
|
|
81
|
-
}
|
|
82
|
-
export declare function createFetchMock(externalServer?: SetupServerLike): FetchMock;
|
|
83
|
-
export {};
|
|
84
|
-
//# sourceMappingURL=mock-server.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mock-server.d.ts","sourceRoot":"","sources":["../src/mock-server.ts"],"names":[],"mappings":"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"}
|