0
# Mock Testing Framework
1
2
Comprehensive testing utilities for mocking HTTP requests, recording interactions, and testing HTTP clients with detailed call history tracking.
3
4
## Capabilities
5
6
### MockAgent
7
8
Main mocking agent that intercepts HTTP requests and provides mock responses for testing.
9
10
```javascript { .api }
11
/**
12
* Main mocking agent for HTTP request interception
13
*/
14
class MockAgent extends Dispatcher {
15
constructor(options?: MockAgent.Options);
16
17
get(origin: string | RegExp | URL): MockPool;
18
close(): Promise<void>;
19
destroy(err?: Error): Promise<void>;
20
21
activate(): void;
22
deactivate(): void;
23
enableNetConnect(host?: string | RegExp | ((host: string) => boolean)): void;
24
disableNetConnect(): void;
25
pendingInterceptors(): PendingInterceptor[];
26
assertNoPendingInterceptors(options?: AssertNoPendingInterceptorsOptions): void;
27
}
28
29
interface MockAgent.Options {
30
agent?: Agent;
31
connections?: number;
32
}
33
34
interface AssertNoPendingInterceptorsOptions {
35
pendingInterceptorsFormatter?: PendingInterceptorsFormatter;
36
}
37
38
type PendingInterceptorsFormatter = (pendingInterceptors: PendingInterceptor[]) => string;
39
```
40
41
**Usage Examples:**
42
43
```javascript
44
import { MockAgent, setGlobalDispatcher } from 'undici';
45
46
// Create mock agent
47
const mockAgent = new MockAgent();
48
49
// Set as global dispatcher to intercept all requests
50
setGlobalDispatcher(mockAgent);
51
52
// Allow connections to specific hosts
53
mockAgent.enableNetConnect('api.github.com');
54
55
// Get mock pool for specific origin
56
const mockPool = mockAgent.get('https://api.example.com');
57
58
// Create mock interceptor
59
mockPool.intercept({
60
path: '/users',
61
method: 'GET'
62
}).reply(200, { users: [{ id: 1, name: 'Alice' }] });
63
64
// Make request (will be intercepted)
65
const response = await fetch('https://api.example.com/users');
66
const data = await response.json();
67
console.log(data); // { users: [{ id: 1, name: 'Alice' }] }
68
69
// Assert all interceptors were used
70
mockAgent.assertNoPendingInterceptors();
71
72
// Clean up
73
await mockAgent.close();
74
```
75
76
### MockPool
77
78
Mock pool for intercepting requests to a specific origin with detailed interceptor management.
79
80
```javascript { .api }
81
/**
82
* Mock pool for specific origin request interception
83
*/
84
class MockPool extends Dispatcher {
85
intercept(options: MockInterceptor.Options): MockInterceptor;
86
close(): Promise<void>;
87
destroy(err?: Error): Promise<void>;
88
}
89
90
interface MockInterceptor {
91
reply(statusCode: number, responseBody?: any, responseHeaders?: Record<string, string>): MockScope;
92
reply(callback: MockInterceptor.MockReplyCallback): MockScope;
93
replyWithError(error: Error): MockScope;
94
defaultReplyHeaders(headers: Record<string, string>): MockInterceptor;
95
defaultReplyTrailers(trailers: Record<string, string>): MockInterceptor;
96
replyContentLength(): MockInterceptor;
97
}
98
99
interface MockInterceptor.Options {
100
path: string | RegExp | ((path: string) => boolean);
101
method?: string | RegExp;
102
body?: string | Buffer | Uint8Array | RegExp | ((body: string) => boolean);
103
headers?: Record<string, string | RegExp | ((headerValue: string) => boolean)>;
104
query?: Record<string, any>;
105
}
106
107
type MockInterceptor.MockReplyCallback = (options: {
108
path: string;
109
origin: string;
110
method: string;
111
body: any;
112
headers: Record<string, string>;
113
}) => {
114
statusCode: number;
115
data?: any;
116
responseOptions?: {
117
headers?: Record<string, string>;
118
trailers?: Record<string, string>;
119
};
120
};
121
122
interface MockScope {
123
delay(waitInMs: number): MockScope;
124
persist(): MockScope;
125
times(repeatTimes: number): MockScope;
126
}
127
```
128
129
**Usage Examples:**
130
131
```javascript
132
import { MockAgent, setGlobalDispatcher } from 'undici';
133
134
const mockAgent = new MockAgent();
135
setGlobalDispatcher(mockAgent);
136
137
const mockPool = mockAgent.get('https://api.example.com');
138
139
// Basic mock with static response
140
mockPool.intercept({
141
path: '/users/123',
142
method: 'GET'
143
}).reply(200, { id: 123, name: 'Alice' });
144
145
// Mock with custom headers
146
mockPool.intercept({
147
path: '/api/data',
148
method: 'POST',
149
headers: {
150
'content-type': 'application/json',
151
'authorization': /^Bearer .+/
152
}
153
}).reply(201, { success: true }, {
154
'location': '/api/data/456'
155
});
156
157
// Mock with body matching
158
mockPool.intercept({
159
path: '/api/search',
160
method: 'POST',
161
body: (body) => {
162
const data = JSON.parse(body);
163
return data.query === 'test';
164
}
165
}).reply(200, { results: ['item1', 'item2'] });
166
167
// Mock with query parameters
168
mockPool.intercept({
169
path: '/api/users',
170
method: 'GET',
171
query: { limit: '10', offset: '0' }
172
}).reply(200, { users: [], total: 0 });
173
174
// Dynamic response with callback
175
mockPool.intercept({
176
path: '/api/echo',
177
method: 'POST'
178
}).reply(({ body, headers }) => {
179
return {
180
statusCode: 200,
181
data: { echoed: body, receivedHeaders: headers }
182
};
183
});
184
185
// Mock with scope options
186
mockPool.intercept({
187
path: '/api/slow',
188
method: 'GET'
189
}).reply(200, { data: 'slow response' })
190
.delay(1000) // 1 second delay
191
.times(3) // Only match 3 times
192
.persist(); // Persist after times limit
193
194
// Mock error responses
195
mockPool.intercept({
196
path: '/api/error',
197
method: 'GET'
198
}).replyWithError(new Error('Network error'));
199
200
await mockAgent.close();
201
```
202
203
### MockClient
204
205
Mock client for testing single connection scenarios.
206
207
```javascript { .api }
208
/**
209
* Mock client for single connection testing
210
*/
211
class MockClient extends Dispatcher {
212
constructor(origin: string, options?: MockClient.Options);
213
214
intercept(options: MockInterceptor.Options): MockInterceptor;
215
close(): Promise<void>;
216
destroy(err?: Error): Promise<void>;
217
}
218
219
interface MockClient.Options {
220
agent?: Agent;
221
}
222
```
223
224
**Usage Examples:**
225
226
```javascript
227
import { MockClient } from 'undici';
228
229
// Create mock client for specific origin
230
const mockClient = new MockClient('https://api.example.com');
231
232
// Setup interceptors
233
mockClient.intercept({
234
path: '/health',
235
method: 'GET'
236
}).reply(200, { status: 'ok' });
237
238
// Use mock client directly
239
const response = await mockClient.request({
240
path: '/health',
241
method: 'GET'
242
});
243
244
const data = await response.body.json();
245
console.log(data); // { status: 'ok' }
246
247
await mockClient.close();
248
```
249
250
### SnapshotAgent
251
252
Recording agent for creating HTTP interaction snapshots that can be replayed in tests.
253
254
```javascript { .api }
255
/**
256
* Recording agent for HTTP interaction snapshots
257
*/
258
class SnapshotAgent extends Dispatcher {
259
constructor(origin: string, dispatcher: Dispatcher);
260
261
record(options?: SnapshotAgent.RecordOptions): void;
262
replay(options?: SnapshotAgent.ReplayOptions): void;
263
getRecording(): SnapshotRecording[];
264
setRecording(recording: SnapshotRecording[]): void;
265
266
close(): Promise<void>;
267
destroy(err?: Error): Promise<void>;
268
}
269
270
interface SnapshotAgent.RecordOptions {
271
filter?: (request: SnapshotRequest) => boolean;
272
}
273
274
interface SnapshotAgent.ReplayOptions {
275
strict?: boolean;
276
}
277
278
interface SnapshotRecording {
279
request: SnapshotRequest;
280
response: SnapshotResponse;
281
timestamp: number;
282
}
283
284
interface SnapshotRequest {
285
method: string;
286
path: string;
287
headers: Record<string, string>;
288
body?: string;
289
}
290
291
interface SnapshotResponse {
292
statusCode: number;
293
headers: Record<string, string>;
294
body: string;
295
}
296
```
297
298
**Usage Examples:**
299
300
```javascript
301
import { SnapshotAgent, Agent } from 'undici';
302
import { writeFileSync, readFileSync } from 'fs';
303
304
const realAgent = new Agent();
305
const snapshotAgent = new SnapshotAgent('https://api.example.com', realAgent);
306
307
// Record real HTTP interactions
308
snapshotAgent.record();
309
310
// Make real requests (these will be recorded)
311
await snapshotAgent.request({ path: '/users' });
312
await snapshotAgent.request({ path: '/posts' });
313
314
// Save recording to file
315
const recording = snapshotAgent.getRecording();
316
writeFileSync('test-recording.json', JSON.stringify(recording, null, 2));
317
318
// Later, in tests: load and replay recording
319
const savedRecording = JSON.parse(readFileSync('test-recording.json', 'utf8'));
320
snapshotAgent.setRecording(savedRecording);
321
snapshotAgent.replay();
322
323
// Requests will now return recorded responses
324
const response = await snapshotAgent.request({ path: '/users' });
325
console.log('Replayed response:', await response.body.json());
326
327
await snapshotAgent.close();
328
```
329
330
### MockCallHistory
331
332
Track and verify mock call history for detailed test assertions.
333
334
```javascript { .api }
335
/**
336
* Track mock call history for test verification
337
*/
338
class MockCallHistory {
339
constructor();
340
341
log(call: MockCallHistoryLog): void;
342
getCalls(filter?: MockCallHistoryFilter): MockCallHistoryLog[];
343
getCallCount(filter?: MockCallHistoryFilter): number;
344
hasBeenCalledWith(expectedCall: Partial<MockCallHistoryLog>): boolean;
345
clear(): void;
346
}
347
348
interface MockCallHistoryLog {
349
method: string;
350
path: string;
351
headers: Record<string, string>;
352
body?: any;
353
timestamp: number;
354
origin: string;
355
}
356
357
type MockCallHistoryFilter = (call: MockCallHistoryLog) => boolean;
358
```
359
360
**Usage Examples:**
361
362
```javascript
363
import { MockAgent, MockCallHistory, setGlobalDispatcher } from 'undici';
364
365
const mockAgent = new MockAgent();
366
const callHistory = new MockCallHistory();
367
setGlobalDispatcher(mockAgent);
368
369
// Setup mock with call logging
370
const mockPool = mockAgent.get('https://api.example.com');
371
mockPool.intercept({
372
path: '/api/users',
373
method: 'POST'
374
}).reply(201, { id: 123 });
375
376
// Intercept and log all calls (this would be done internally by undici)
377
const originalRequest = mockPool.request;
378
mockPool.request = function(options) {
379
callHistory.log({
380
method: options.method || 'GET',
381
path: options.path,
382
headers: options.headers || {},
383
body: options.body,
384
timestamp: Date.now(),
385
origin: 'https://api.example.com'
386
});
387
return originalRequest.call(this, options);
388
};
389
390
// Make requests
391
await fetch('https://api.example.com/api/users', {
392
method: 'POST',
393
body: JSON.stringify({ name: 'Alice' })
394
});
395
396
// Verify call history
397
console.log(callHistory.getCallCount()); // 1
398
399
const calls = callHistory.getCalls();
400
console.log(calls[0].method); // 'POST'
401
console.log(calls[0].path); // '/api/users'
402
403
// Check specific call
404
const wasCalledWithCorrectData = callHistory.hasBeenCalledWith({
405
method: 'POST',
406
path: '/api/users'
407
});
408
console.log(wasCalledWithCorrectData); // true
409
410
// Filter calls
411
const postCalls = callHistory.getCalls(call => call.method === 'POST');
412
console.log(postCalls.length); // 1
413
414
await mockAgent.close();
415
```
416
417
### Mock Errors
418
419
Specialized error types for mock testing scenarios.
420
421
```javascript { .api }
422
/**
423
* Mock-specific error types
424
*/
425
const mockErrors: {
426
MockNotMatchedError: typeof MockNotMatchedError;
427
MockInterceptorMismatchError: typeof MockInterceptorMismatchError;
428
};
429
430
class MockNotMatchedError extends Error {
431
constructor(message: string);
432
}
433
434
class MockInterceptorMismatchError extends Error {
435
constructor(message: string);
436
}
437
```
438
439
**Usage Examples:**
440
441
```javascript
442
import { MockAgent, mockErrors, setGlobalDispatcher } from 'undici';
443
444
const mockAgent = new MockAgent();
445
setGlobalDispatcher(mockAgent);
446
447
// Disable real network connections
448
mockAgent.disableNetConnect();
449
450
try {
451
// This will throw since no mock is defined
452
await fetch('https://api.example.com/unmocked');
453
} catch (error) {
454
if (error instanceof mockErrors.MockNotMatchedError) {
455
console.log('No mock interceptor matched the request');
456
}
457
}
458
459
// Setup specific error scenarios
460
const mockPool = mockAgent.get('https://api.example.com');
461
mockPool.intercept({
462
path: '/api/error-test',
463
method: 'GET'
464
}).replyWithError(new mockErrors.MockInterceptorMismatchError('Intentional test error'));
465
466
await mockAgent.close();
467
```
468
469
## Complete Testing Workflow
470
471
```javascript
472
import { MockAgent, setGlobalDispatcher, MockCallHistory } from 'undici';
473
import { test, beforeEach, afterEach } from 'node:test';
474
import assert from 'node:assert';
475
476
let mockAgent;
477
let callHistory;
478
479
beforeEach(() => {
480
mockAgent = new MockAgent();
481
callHistory = new MockCallHistory();
482
setGlobalDispatcher(mockAgent);
483
484
// Disable real network connections in tests
485
mockAgent.disableNetConnect();
486
});
487
488
afterEach(async () => {
489
await mockAgent.close();
490
});
491
492
test('user service integration', async () => {
493
const mockPool = mockAgent.get('https://api.example.com');
494
495
// Mock user creation
496
mockPool.intercept({
497
path: '/users',
498
method: 'POST',
499
body: (body) => {
500
const data = JSON.parse(body);
501
return data.name && data.email;
502
}
503
}).reply(201, { id: 123, name: 'Alice', email: 'alice@example.com' });
504
505
// Mock user retrieval
506
mockPool.intercept({
507
path: '/users/123',
508
method: 'GET'
509
}).reply(200, { id: 123, name: 'Alice', email: 'alice@example.com' });
510
511
// Test the actual service
512
const createResponse = await fetch('https://api.example.com/users', {
513
method: 'POST',
514
headers: { 'content-type': 'application/json' },
515
body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
516
});
517
518
assert.strictEqual(createResponse.status, 201);
519
520
const getResponse = await fetch('https://api.example.com/users/123');
521
const userData = await getResponse.json();
522
523
assert.strictEqual(userData.name, 'Alice');
524
assert.strictEqual(userData.email, 'alice@example.com');
525
526
// Verify all mocks were called
527
mockAgent.assertNoPendingInterceptors();
528
});
529
```