0
# Mocking System
1
2
Comprehensive mocking system for functions and object methods with call tracking, implementation control, and restoration capabilities. The mocking system provides fine-grained control over function behavior during testing.
3
4
## Capabilities
5
6
### MockTracker
7
8
The main mocking interface accessed via the `mock` property. Provides methods for creating and managing various types of mocks.
9
10
```javascript { .api }
11
/**
12
* Main mocking interface for creating and managing mocks
13
*/
14
const mock: MockTracker;
15
16
interface MockTracker {
17
/** Create a function mock */
18
fn(original?: Function, implementation?: Function, options?: MockOptions): MockFunctionContext;
19
/** Mock an object method */
20
method(object: object, methodName: string, implementation?: Function, options?: MethodMockOptions): MockFunctionContext;
21
/** Mock an object getter */
22
getter(object: object, methodName: string, implementation?: Function, options?: MockOptions): MockFunctionContext;
23
/** Mock an object setter */
24
setter(object: object, methodName: string, implementation?: Function, options?: MockOptions): MockFunctionContext;
25
/** Reset all mock call tracking data */
26
reset(): void;
27
/** Restore all mocked functions to their originals */
28
restoreAll(): void;
29
}
30
```
31
32
### Function Mocking
33
34
Create mock functions for testing function behavior and call patterns.
35
36
```javascript { .api }
37
/**
38
* Create a function mock
39
* @param original - Original function to mock (optional)
40
* @param implementation - Mock implementation (optional)
41
* @param options - Mock configuration options
42
* @returns MockFunctionContext for controlling and inspecting the mock
43
*/
44
fn(original?: Function, implementation?: Function, options?: MockOptions): MockFunctionContext;
45
46
interface MockOptions {
47
/** Number of times mock should be active before restoring */
48
times?: number;
49
}
50
```
51
52
**Usage Examples:**
53
54
```javascript
55
import { mock } from "test";
56
57
// Basic function mock
58
const mockFn = mock.fn();
59
mockFn('hello', 'world');
60
console.log(mockFn.callCount()); // 1
61
62
// Mock with implementation
63
const add = mock.fn(null, (a, b) => a + b);
64
console.log(add(2, 3)); // 5
65
66
// Mock existing function
67
const originalFetch = fetch;
68
const mockFetch = mock.fn(originalFetch, async () => ({
69
json: async () => ({ success: true })
70
}));
71
72
// Temporary mock (auto-restores after 2 calls)
73
const tempMock = mock.fn(console.log, () => {}, { times: 2 });
74
tempMock('call 1'); // mock implementation
75
tempMock('call 2'); // mock implementation
76
tempMock('call 3'); // original console.log
77
```
78
79
### Method Mocking
80
81
Mock methods on existing objects while preserving the object structure.
82
83
```javascript { .api }
84
/**
85
* Mock an object method
86
* @param object - Target object containing the method
87
* @param methodName - Name of the method to mock
88
* @param implementation - Mock implementation (optional)
89
* @param options - Mock configuration options
90
* @returns MockFunctionContext for controlling and inspecting the mock
91
*/
92
method(object: object, methodName: string, implementation?: Function, options?: MethodMockOptions): MockFunctionContext;
93
94
interface MethodMockOptions extends MockOptions {
95
/** Mock as getter property */
96
getter?: boolean;
97
/** Mock as setter property */
98
setter?: boolean;
99
}
100
```
101
102
**Usage Examples:**
103
104
```javascript
105
import { mock } from "test";
106
107
// Mock object method
108
const user = {
109
name: 'Alice',
110
getName() { return this.name; }
111
};
112
113
const mockGetName = mock.method(user, 'getName', function() {
114
return 'Mocked Name';
115
});
116
117
console.log(user.getName()); // 'Mocked Name'
118
console.log(mockGetName.callCount()); // 1
119
120
// Mock with original function access
121
const fs = require('fs');
122
const mockReadFile = mock.method(fs, 'readFileSync', (path) => {
123
if (path === 'test.txt') return 'mocked content';
124
// Call original for other files
125
return mockReadFile.original(path);
126
});
127
128
// Mock getter/setter
129
const config = {
130
_timeout: 1000,
131
get timeout() { return this._timeout; },
132
set timeout(value) { this._timeout = value; }
133
};
134
135
mock.method(config, 'timeout', () => 5000, { getter: true });
136
console.log(config.timeout); // 5000
137
```
138
139
### Getter and Setter Mocking
140
141
Specialized methods for mocking property getters and setters.
142
143
```javascript { .api }
144
/**
145
* Mock an object getter
146
* @param object - Target object
147
* @param methodName - Property name
148
* @param implementation - Getter implementation
149
* @param options - Mock options
150
* @returns MockFunctionContext
151
*/
152
getter(object: object, methodName: string, implementation?: Function, options?: MockOptions): MockFunctionContext;
153
154
/**
155
* Mock an object setter
156
* @param object - Target object
157
* @param methodName - Property name
158
* @param implementation - Setter implementation
159
* @param options - Mock options
160
* @returns MockFunctionContext
161
*/
162
setter(object: object, methodName: string, implementation?: Function, options?: MockOptions): MockFunctionContext;
163
```
164
165
**Usage Examples:**
166
167
```javascript
168
import { mock } from "test";
169
170
const obj = {
171
_value: 42,
172
get value() { return this._value; },
173
set value(v) { this._value = v; }
174
};
175
176
// Mock getter
177
const mockGetter = mock.getter(obj, 'value', () => 999);
178
console.log(obj.value); // 999
179
180
// Mock setter
181
const mockSetter = mock.setter(obj, 'value', function(v) {
182
console.log(`Setting value to ${v}`);
183
this._value = v * 2;
184
});
185
186
obj.value = 10; // Logs: "Setting value to 10"
187
console.log(obj._value); // 20
188
```
189
190
## MockFunctionContext
191
192
Context object returned by all mock creation methods, providing control and inspection capabilities.
193
194
```javascript { .api }
195
interface MockFunctionContext {
196
/** Array of all function calls (read-only) */
197
calls: CallRecord[];
198
/** Get the total number of calls */
199
callCount(): number;
200
/** Change the mock implementation */
201
mockImplementation(implementation: Function): void;
202
/** Set implementation for a specific call number */
203
mockImplementationOnce(implementation: Function, onCall?: number): void;
204
/** Restore the original function */
205
restore(): void;
206
}
207
208
interface CallRecord {
209
arguments: any[];
210
result?: any;
211
error?: Error;
212
target?: any;
213
this?: any;
214
}
215
```
216
217
**Usage Examples:**
218
219
```javascript
220
import { mock } from "test";
221
222
const mockFn = mock.fn();
223
224
// Make some calls
225
mockFn('arg1', 'arg2');
226
mockFn(123);
227
mockFn();
228
229
// Inspect calls
230
console.log(mockFn.callCount()); // 3
231
console.log(mockFn.calls[0].arguments); // ['arg1', 'arg2']
232
console.log(mockFn.calls[1].arguments); // [123]
233
234
// Change implementation
235
mockFn.mockImplementation((x) => x * 2);
236
console.log(mockFn(5)); // 10
237
238
// One-time implementation
239
mockFn.mockImplementationOnce(() => 'special', 4);
240
mockFn(); // 'special' (5th call)
241
mockFn(); // back to x * 2 implementation
242
243
// Restore original
244
mockFn.restore();
245
```
246
247
### Call Inspection
248
249
Detailed inspection of mock function calls:
250
251
```javascript
252
import { mock } from "test";
253
254
const calculator = {
255
add(a, b) { return a + b; }
256
};
257
258
const mockAdd = mock.method(calculator, 'add');
259
260
// Make calls
261
calculator.add(2, 3);
262
calculator.add(10, 5);
263
264
// Inspect calls
265
const calls = mockAdd.calls;
266
267
calls.forEach((call, index) => {
268
console.log(`Call ${index + 1}:`);
269
console.log(` Arguments: ${call.arguments}`);
270
console.log(` Result: ${call.result}`);
271
console.log(` This context:`, call.this);
272
});
273
274
// Check specific calls
275
if (mockAdd.callCount() >= 2) {
276
const secondCall = calls[1];
277
if (secondCall.arguments[0] === 10 && secondCall.arguments[1] === 5) {
278
console.log('Second call was add(10, 5)');
279
}
280
}
281
```
282
283
## Global Mock Management
284
285
### Reset All Mocks
286
287
Clear call tracking data for all mocks without restoring implementations:
288
289
```javascript { .api }
290
/**
291
* Reset all mock call tracking data
292
*/
293
reset(): void;
294
```
295
296
**Usage Examples:**
297
298
```javascript
299
import { mock } from "test";
300
301
const mockFn1 = mock.fn();
302
const mockFn2 = mock.fn();
303
304
mockFn1('test');
305
mockFn2('test');
306
307
console.log(mockFn1.callCount()); // 1
308
console.log(mockFn2.callCount()); // 1
309
310
mock.reset();
311
312
console.log(mockFn1.callCount()); // 0
313
console.log(mockFn2.callCount()); // 0
314
// Functions still mocked, just call history cleared
315
```
316
317
### Restore All Mocks
318
319
Restore all mocked functions to their original implementations:
320
321
```javascript { .api }
322
/**
323
* Restore all mocked functions to their originals
324
*/
325
restoreAll(): void;
326
```
327
328
**Usage Examples:**
329
330
```javascript
331
import { mock } from "test";
332
333
const obj = {
334
method1() { return 'original1'; },
335
method2() { return 'original2'; }
336
};
337
338
mock.method(obj, 'method1', () => 'mocked1');
339
mock.method(obj, 'method2', () => 'mocked2');
340
341
console.log(obj.method1()); // 'mocked1'
342
console.log(obj.method2()); // 'mocked2'
343
344
mock.restoreAll();
345
346
console.log(obj.method1()); // 'original1'
347
console.log(obj.method2()); // 'original2'
348
```
349
350
## Advanced Patterns
351
352
### Conditional Mocking
353
354
Mock functions that behave differently based on arguments:
355
356
```javascript
357
import { mock } from "test";
358
359
const mockFetch = mock.fn(fetch, async (url, options) => {
360
if (url.includes('/api/users')) {
361
return { json: async () => [{ id: 1, name: 'Test User' }] };
362
}
363
if (url.includes('/api/error')) {
364
throw new Error('Network error');
365
}
366
// Call original fetch for other URLs
367
return fetch(url, options);
368
});
369
```
370
371
### Spy Pattern
372
373
Monitor function calls without changing behavior:
374
375
```javascript
376
import { mock } from "test";
377
378
const logger = {
379
log(message) {
380
console.log(`[LOG] ${message}`);
381
}
382
};
383
384
// Spy on logger.log (keep original behavior)
385
const logSpy = mock.method(logger, 'log', function(message) {
386
// Call original implementation
387
return logSpy.original.call(this, message);
388
});
389
390
logger.log('Hello'); // Still logs to console
391
console.log(logSpy.callCount()); // 1
392
console.log(logSpy.calls[0].arguments[0]); // 'Hello'
393
```
394
395
### Mock Chaining
396
397
Create complex mock setups:
398
399
```javascript
400
import { mock } from "test";
401
402
const api = {
403
get() { return Promise.resolve({ data: 'real data' }); },
404
post() { return Promise.resolve({ success: true }); }
405
};
406
407
// Chain multiple mocks
408
mock.method(api, 'get', () => Promise.resolve({ data: 'mock data' }))
409
mock.method(api, 'post', () => Promise.resolve({ success: false }));
410
411
// Test with mocked API
412
test('API interactions', async (t) => {
413
const getData = await api.get();
414
const postResult = await api.post();
415
416
if (getData.data !== 'mock data') {
417
throw new Error('GET mock failed');
418
}
419
if (postResult.success !== false) {
420
throw new Error('POST mock failed');
421
}
422
423
// Clean up
424
mock.restoreAll();
425
});
426
```
427
428
### Error Simulation
429
430
Mock functions to simulate error conditions:
431
432
```javascript
433
import { mock } from "test";
434
435
const fileSystem = {
436
readFile(path) {
437
// Original implementation
438
return fs.readFileSync(path, 'utf8');
439
}
440
};
441
442
// Mock to simulate file not found
443
const mockReadFile = mock.method(fileSystem, 'readFile', (path) => {
444
if (path === 'nonexistent.txt') {
445
const error = new Error('ENOENT: no such file or directory');
446
error.code = 'ENOENT';
447
throw error;
448
}
449
return 'file contents';
450
});
451
452
test('error handling', (t) => {
453
try {
454
fileSystem.readFile('nonexistent.txt');
455
throw new Error('Should have thrown');
456
} catch (error) {
457
if (error.code !== 'ENOENT') {
458
throw new Error('Wrong error type');
459
}
460
}
461
462
// Verify the error was tracked
463
if (mockReadFile.calls[0].error.code !== 'ENOENT') {
464
throw new Error('Error not tracked correctly');
465
}
466
});
467
```
468
469
## Best Practices
470
471
1. **Clean up mocks**: Always restore mocks after tests
472
2. **Use spies for monitoring**: Keep original behavior when you just need to track calls
473
3. **Mock at the right level**: Mock dependencies, not implementation details
474
4. **Test mock behavior**: Verify mocks are called with expected arguments
475
5. **Avoid over-mocking**: Only mock what's necessary for the test
476
477
```javascript
478
import { mock, afterEach } from "test";
479
480
// Clean up after each test
481
afterEach(() => {
482
mock.restoreAll();
483
});
484
485
test('user service', (t) => {
486
const database = {
487
findUser: (id) => ({ id, name: 'Real User' })
488
};
489
490
// Mock external dependency
491
const mockFindUser = mock.method(database, 'findUser',
492
(id) => ({ id, name: 'Test User' })
493
);
494
495
const userService = new UserService(database);
496
const user = userService.getUser(123);
497
498
// Test the result
499
if (user.name !== 'Test User') {
500
throw new Error('Service failed');
501
}
502
503
// Verify mock was called correctly
504
if (mockFindUser.callCount() !== 1) {
505
throw new Error('Database not called');
506
}
507
if (mockFindUser.calls[0].arguments[0] !== 123) {
508
throw new Error('Wrong user ID passed');
509
}
510
});
511
```