0
# Mocking and Interception
1
2
Module mocking, function interception, and call capturing for isolating tests and controlling dependencies.
3
4
## Capabilities
5
6
### Module Mocking
7
8
Mock Node.js modules to control dependencies and isolate units under test.
9
10
```typescript { .api }
11
/**
12
* Mock a CommonJS module with custom implementations
13
* @param module - Module name or path to mock
14
* @param mocks - Object containing mock implementations
15
*/
16
function mockRequire(module: string, mocks?: any): void;
17
18
/**
19
* Mock an ES module with custom implementations
20
* @param module - Module name or path to mock
21
* @param mocks - Object containing mock implementations
22
* @returns Promise that resolves when mock is registered
23
*/
24
function mockImport(module: string, mocks?: any): Promise<void>;
25
26
/**
27
* Create a mock object with base properties and overrides
28
* @param base - Base object to extend (optional)
29
* @param overrides - Properties to override or add
30
* @returns Mock object with specified properties
31
*/
32
function createMock(base?: any, overrides?: any): any;
33
```
34
35
**Usage Examples:**
36
37
```typescript
38
import { test, mockRequire, mockImport } from "tap";
39
40
// Mock CommonJS module
41
test("mock CommonJS fs module", (t) => {
42
mockRequire("fs", {
43
readFileSync: () => "mocked file content",
44
writeFileSync: () => undefined,
45
existsSync: () => true
46
});
47
48
const fs = require("fs");
49
const content = fs.readFileSync("any-file.txt");
50
t.equal(content, "mocked file content");
51
t.end();
52
});
53
54
// Mock ESM module
55
test("mock ES module", async (t) => {
56
await mockImport("node:fs/promises", {
57
readFile: async () => "mocked async content",
58
writeFile: async () => undefined
59
});
60
61
const { readFile } = await import("node:fs/promises");
62
const content = await readFile("any-file.txt");
63
t.equal(content, "mocked async content");
64
});
65
66
// Create mock objects
67
test("create mock objects", (t) => {
68
const mockUser = createMock({ id: 1 }, {
69
name: "Test User",
70
save: () => Promise.resolve()
71
});
72
73
t.equal(mockUser.id, 1);
74
t.equal(mockUser.name, "Test User");
75
t.type(mockUser.save, "function");
76
t.end();
77
});
78
```
79
80
### Function Interception
81
82
Intercept and monitor function calls for testing behavior and side effects.
83
84
```typescript { .api }
85
/**
86
* Intercept property access on an object
87
* @param object - Target object to intercept
88
* @param property - Property name to intercept
89
* @param options - Interception configuration options
90
*/
91
function intercept(object: any, property: string, options?: InterceptOpts): void;
92
93
/**
94
* Capture function calls with detailed information
95
* @param fn - Function to capture calls for
96
* @returns Captured function with call information
97
*/
98
function captureFn(fn: Function): CapturedFunction;
99
100
/**
101
* Capture method calls on an object
102
* @param object - Target object
103
* @param property - Method name to capture
104
* @param fn - Optional replacement function
105
* @returns Original method for restoration
106
*/
107
function capture(object: any, property: string, fn?: Function): Function;
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
// Intercept console.log calls
114
test("intercept console output", (t) => {
115
const messages: string[] = [];
116
117
intercept(console, "log", {
118
value: (message: string) => {
119
messages.push(message);
120
}
121
});
122
123
console.log("Hello");
124
console.log("World");
125
126
t.same(messages, ["Hello", "World"]);
127
t.end();
128
});
129
130
// Capture function calls
131
test("capture function calls", (t) => {
132
const originalFn = (x: number, y: number) => x + y;
133
const captured = captureFn(originalFn);
134
135
const result1 = captured(2, 3);
136
const result2 = captured(5, 7);
137
138
t.equal(result1, 5);
139
t.equal(result2, 12);
140
t.equal(captured.calls.length, 2);
141
t.same(captured.calls[0].args, [2, 3]);
142
t.same(captured.calls[1].args, [5, 7]);
143
t.end();
144
});
145
146
// Capture method calls
147
test("capture method calls", (t) => {
148
const api = {
149
save: (data: any) => Promise.resolve({ id: 1, ...data }),
150
delete: (id: number) => Promise.resolve()
151
};
152
153
const originalSave = capture(api, "save");
154
155
api.save({ name: "test" });
156
api.save({ name: "test2" });
157
158
// Access captured calls
159
t.equal(api.save.calls.length, 2);
160
t.same(api.save.calls[0].args, [{ name: "test" }]);
161
162
// Restore original
163
api.save = originalSave;
164
t.end();
165
});
166
```
167
168
### Advanced Mocking Patterns
169
170
More sophisticated mocking scenarios for complex testing needs.
171
172
```typescript
173
// Mock with dynamic behavior
174
test("dynamic mock behavior", (t) => {
175
let callCount = 0;
176
177
mockRequire("axios", {
178
get: (url: string) => {
179
callCount++;
180
if (callCount === 1) {
181
return Promise.resolve({ data: "first call" });
182
} else {
183
return Promise.reject(new Error("rate limited"));
184
}
185
}
186
});
187
188
const axios = require("axios");
189
190
axios.get("http://api.example.com")
191
.then((response: any) => {
192
t.equal(response.data, "first call");
193
194
return axios.get("http://api.example.com");
195
})
196
.catch((error: Error) => {
197
t.match(error.message, /rate limited/);
198
t.end();
199
});
200
});
201
202
// Mock with state tracking
203
test("stateful mocks", (t) => {
204
const mockDatabase = {
205
data: new Map(),
206
207
save: function(id: string, value: any) {
208
this.data.set(id, value);
209
return Promise.resolve(value);
210
},
211
212
find: function(id: string) {
213
return Promise.resolve(this.data.get(id));
214
},
215
216
clear: function() {
217
this.data.clear();
218
}
219
};
220
221
mockRequire("./database", mockDatabase);
222
223
const db = require("./database");
224
225
db.save("user1", { name: "Alice" })
226
.then(() => db.find("user1"))
227
.then((user: any) => {
228
t.same(user, { name: "Alice" });
229
t.end();
230
});
231
});
232
```
233
234
### Partial Mocking
235
236
Mock only specific parts of modules while preserving other functionality.
237
238
```typescript
239
test("partial module mocking", async (t) => {
240
// Only mock specific methods of fs module
241
const originalFs = await import("node:fs/promises");
242
243
await mockImport("node:fs/promises", {
244
...originalFs,
245
readFile: async () => "mocked content",
246
// Other methods remain unchanged
247
});
248
249
const fs = await import("node:fs/promises");
250
const content = await fs.readFile("test.txt");
251
252
t.equal(content, "mocked content");
253
// fs.writeFile, fs.stat, etc. still work normally
254
});
255
```
256
257
### Mock Cleanup and Restoration
258
259
Properly clean up mocks to avoid test interference.
260
261
```typescript
262
test("mock cleanup", (t) => {
263
// Store original for restoration
264
const originalLog = console.log;
265
let logMessages: string[] = [];
266
267
intercept(console, "log", {
268
value: (message: string) => {
269
logMessages.push(message);
270
}
271
});
272
273
console.log("test message");
274
275
t.same(logMessages, ["test message"]);
276
277
// Restore original (TAP usually handles this automatically)
278
console.log = originalLog;
279
280
t.end();
281
});
282
```
283
284
### Types
285
286
```typescript { .api }
287
interface InterceptOpts {
288
/** Replacement value or function */
289
value?: any;
290
291
/** Whether to call original after intercept */
292
callOriginal?: boolean;
293
294
/** Function to call before original */
295
before?: Function;
296
297
/** Function to call after original */
298
after?: Function;
299
}
300
301
interface CapturedFunction extends Function {
302
/** Array of captured call information */
303
calls: CallInfo[];
304
305
/** Original function that was captured */
306
original: Function;
307
308
/** Reset captured calls */
309
reset(): void;
310
}
311
312
interface CallInfo {
313
/** Arguments passed to the function */
314
args: any[];
315
316
/** Return value of the function call */
317
result?: any;
318
319
/** Error thrown by the function (if any) */
320
error?: Error;
321
322
/** Timestamp of the call */
323
timestamp: number;
324
325
/** Context (this value) of the call */
326
context?: any;
327
}
328
```
329
330
### Testing with External Dependencies
331
332
Common patterns for mocking external services and dependencies.
333
334
```typescript
335
// Mock HTTP client
336
test("HTTP service testing", async (t) => {
337
await mockImport("node:http", {
338
request: (options: any, callback: Function) => {
339
const mockResponse = {
340
statusCode: 200,
341
headers: { "content-type": "application/json" },
342
on: (event: string, handler: Function) => {
343
if (event === "data") {
344
handler('{"success": true}');
345
}
346
if (event === "end") {
347
handler();
348
}
349
}
350
};
351
callback(mockResponse);
352
return { end: () => {}, on: () => {} };
353
}
354
});
355
356
// Your HTTP service code here
357
// const result = await httpService.get("http://api.example.com");
358
// t.ok(result.success);
359
});
360
361
// Mock environment variables
362
test("environment-dependent code", (t) => {
363
const originalEnv = process.env.NODE_ENV;
364
process.env.NODE_ENV = "test";
365
366
// Test environment-specific behavior
367
t.equal(getEnvironment(), "test");
368
369
// Restore
370
process.env.NODE_ENV = originalEnv;
371
t.end();
372
});
373
374
// Mock timers
375
test("time-dependent code", (t) => {
376
const originalSetTimeout = global.setTimeout;
377
let timeoutCallback: Function;
378
379
global.setTimeout = (callback: Function, delay: number) => {
380
timeoutCallback = callback;
381
return 1; // Mock timer ID
382
};
383
384
startTimedProcess();
385
386
// Manually trigger timeout
387
timeoutCallback();
388
389
t.ok(isProcessComplete());
390
391
// Restore
392
global.setTimeout = originalSetTimeout;
393
t.end();
394
});
395
```