0
# Mocking and Spying
1
2
Comprehensive mocking through the `vi` namespace (alias: `vitest`) for module mocking, function spying, and test isolation.
3
4
## Mock Functions
5
6
```typescript { .api }
7
vi.fn<Args extends any[], R>(implementation?: (...args: Args) => R): Mock<Args, R>;
8
vi.isMockFunction(fn: any): fn is Mock;
9
10
interface Mock<Args extends any[], R> {
11
(...args: Args): R;
12
13
mock: {
14
calls: Args[];
15
results: MockResult<R>[];
16
instances: any[];
17
invocationCallOrder: number[];
18
lastCall?: Args;
19
};
20
21
mockImplementation(fn: (...args: Args) => R): this;
22
mockImplementationOnce(fn: (...args: Args) => R): this;
23
mockReturnValue(value: R): this;
24
mockReturnValueOnce(value: R): this;
25
mockResolvedValue(value: Awaited<R>): this;
26
mockResolvedValueOnce(value: Awaited<R>): this;
27
mockRejectedValue(value: any): this;
28
mockRejectedValueOnce(value: any): this;
29
mockClear(): this;
30
mockReset(): this;
31
mockRestore(): this;
32
getMockName(): string;
33
mockName(name: string): this;
34
}
35
```
36
37
**Examples:**
38
```typescript
39
// Basic mock
40
const mockFn = vi.fn((x: number) => x * 2);
41
mockFn(5); // Returns 10
42
expect(mockFn).toHaveBeenCalledWith(5);
43
44
// Return values
45
const fn = vi.fn();
46
fn.mockReturnValue(42);
47
fn(); // 42
48
49
fn.mockReturnValueOnce(100).mockReturnValueOnce(200);
50
fn(); // 100
51
fn(); // 200
52
fn(); // 42 (default)
53
54
// Async values
55
const asyncFn = vi.fn();
56
asyncFn.mockResolvedValue('success');
57
await asyncFn(); // 'success'
58
59
asyncFn.mockRejectedValue(new Error('failed'));
60
await asyncFn(); // throws Error
61
62
// Implementation
63
const addFn = vi.fn();
64
addFn.mockImplementation((a, b) => a + b);
65
addFn(2, 3); // 5
66
67
addFn.mockImplementationOnce((a, b) => a * b);
68
addFn(2, 3); // 6
69
addFn(2, 3); // 5 (back to default)
70
```
71
72
## Spying
73
74
```typescript { .api }
75
vi.spyOn<T, K extends keyof T>(
76
object: T,
77
method: K,
78
accessType?: 'get' | 'set'
79
): MockInstance;
80
```
81
82
**Examples:**
83
```typescript
84
// Spy on method (preserves original behavior)
85
const calculator = {
86
add: (a: number, b: number) => a + b,
87
};
88
89
const spy = vi.spyOn(calculator, 'add');
90
calculator.add(2, 3); // Returns 5 (original behavior)
91
expect(spy).toHaveBeenCalledWith(2, 3);
92
spy.mockRestore(); // Restore original
93
94
// Spy on getter
95
const obj = {
96
get value() { return 42; }
97
};
98
99
const spy = vi.spyOn(obj, 'value', 'get');
100
obj.value; // 42
101
expect(spy).toHaveBeenCalled();
102
103
spy.mockReturnValue(100);
104
obj.value; // 100
105
106
// Spy on setter
107
let _value = 0;
108
const obj = {
109
set value(v: number) { _value = v; }
110
};
111
112
const spy = vi.spyOn(obj, 'value', 'set');
113
obj.value = 42;
114
expect(spy).toHaveBeenCalledWith(42);
115
```
116
117
## Module Mocking
118
119
```typescript { .api }
120
// Hoisted mocking (runs before imports)
121
vi.mock(path: string, factory?: () => any): void;
122
vi.unmock(path: string): void;
123
124
// Non-hoisted mocking (for conditional mocks)
125
vi.doMock(path: string, factory?: () => any): void;
126
vi.doUnmock(path: string): void;
127
128
// Import helpers
129
vi.importActual<T>(path: string): Promise<T>;
130
vi.importMock<T>(path: string): Promise<T>;
131
vi.hoisted<T>(factory: () => T): T;
132
```
133
134
**Examples:**
135
```typescript
136
// Mock entire module
137
vi.mock('./api', () => ({
138
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'John' })),
139
deleteUser: vi.fn(() => Promise.resolve(true)),
140
}));
141
142
test('mocked module', async () => {
143
const { fetchUser } = await import('./api');
144
const user = await fetchUser(1);
145
expect(user).toEqual({ id: 1, name: 'John' });
146
});
147
148
// Partial mock (keep some real exports)
149
vi.mock('./utils', async () => {
150
const actual = await vi.importActual<typeof import('./utils')>('./utils');
151
return {
152
...actual,
153
fetchData: vi.fn(() => 'mocked data'),
154
};
155
});
156
157
// Conditional mocking
158
test('conditional mock', async () => {
159
vi.doMock('./config', () => ({ apiUrl: 'https://test.api' }));
160
const { apiUrl } = await import('./config');
161
expect(apiUrl).toBe('https://test.api');
162
vi.doUnmock('./config');
163
});
164
165
// Hoisted factory (share values across mocks)
166
const { mockUsers } = vi.hoisted(() => ({
167
mockUsers: [{ id: 1, name: 'John' }],
168
}));
169
170
vi.mock('./database', () => ({
171
getUsers: vi.fn(() => Promise.resolve(mockUsers)),
172
}));
173
```
174
175
## Mock Object
176
177
Create deep mock of objects.
178
179
```typescript { .api }
180
vi.mockObject<T>(value: T, options?: { spy?: boolean }): MaybeMockedDeep<T>;
181
```
182
183
**Examples:**
184
```typescript
185
// Mock object (methods return undefined)
186
const api = {
187
fetchUser: (id: number) => Promise.resolve({ id, name: 'John' }),
188
deleteUser: (id: number) => Promise.resolve(true),
189
};
190
191
const mockedApi = vi.mockObject(api);
192
expect(vi.isMockFunction(mockedApi.fetchUser)).toBe(true);
193
await expect(mockedApi.fetchUser(1)).resolves.toBeUndefined();
194
195
// Configure mock behavior
196
mockedApi.fetchUser.mockResolvedValue({ id: 1, name: 'Test' });
197
await expect(mockedApi.fetchUser(1)).resolves.toEqual({ id: 1, name: 'Test' });
198
199
// Spy on object (preserves behavior)
200
const api2 = {
201
add: (a: number, b: number) => a + b,
202
multiply: (a: number, b: number) => a * b,
203
};
204
205
const spiedApi = vi.mockObject(api2, { spy: true });
206
expect(spiedApi.add(2, 3)).toBe(5); // Original behavior
207
expect(spiedApi.add).toHaveBeenCalledWith(2, 3); // But tracked
208
```
209
210
## Typed Mocks
211
212
```typescript { .api }
213
vi.mocked<T>(item: T, deep?: false): Mocked<T>;
214
vi.mocked<T>(item: T, deep: true): MaybeMockedDeep<T>;
215
```
216
217
**Example:**
218
```typescript
219
import { fetchUser } from './api';
220
221
vi.mock('./api');
222
223
test('typed mock', () => {
224
const mockFetchUser = vi.mocked(fetchUser);
225
mockFetchUser.mockResolvedValue({ id: 1, name: 'John' });
226
// TypeScript knows about mockResolvedValue
227
});
228
```
229
230
## Mock State Management
231
232
```typescript { .api }
233
vi.clearAllMocks() // Clear call history only
234
vi.resetAllMocks() // Clear history + reset implementation
235
vi.restoreAllMocks() // Restore original implementations (spies)
236
```
237
238
| Method | Call History | Implementation | Restore Original |
239
|--------|--------------|----------------|------------------|
240
| `mockClear()` | ✓ Clear | Keep | No |
241
| `mockReset()` | ✓ Clear | ✓ Reset | No |
242
| `mockRestore()` | ✓ Clear | ✓ Reset | ✓ Yes (spies only) |
243
244
**Example:**
245
```typescript
246
beforeEach(() => {
247
vi.clearAllMocks(); // Clear all mock call history
248
});
249
250
afterEach(() => {
251
vi.restoreAllMocks(); // Restore all spies
252
});
253
254
test('mock cleanup', () => {
255
const mockFn = vi.fn(() => 42);
256
mockFn();
257
mockFn();
258
expect(mockFn).toHaveBeenCalledTimes(2);
259
260
vi.clearAllMocks(); // Clear history, keep implementation
261
expect(mockFn).toHaveBeenCalledTimes(0);
262
expect(mockFn()).toBe(42); // Still returns 42
263
264
vi.resetAllMocks(); // Clear history, reset implementation
265
expect(mockFn()).toBeUndefined();
266
});
267
```
268
269
## Global Stubs
270
271
```typescript { .api }
272
vi.stubGlobal(name: string, value: any): VitestUtils;
273
vi.unstubAllGlobals(): VitestUtils;
274
vi.stubEnv(name: string, value: string): VitestUtils;
275
vi.unstubAllEnvs(): VitestUtils;
276
```
277
278
**Examples:**
279
```typescript
280
afterEach(() => {
281
vi.unstubAllGlobals();
282
vi.unstubAllEnvs();
283
});
284
285
test('stub fetch', () => {
286
const mockFetch = vi.fn(() => Promise.resolve({ ok: true }));
287
vi.stubGlobal('fetch', mockFetch);
288
289
// fetch is now mocked
290
expect(globalThis.fetch).toBe(mockFetch);
291
});
292
293
test('stub env vars', () => {
294
vi.stubEnv('NODE_ENV', 'test');
295
vi.stubEnv('API_KEY', 'test-key');
296
297
expect(process.env.NODE_ENV).toBe('test');
298
expect(process.env.API_KEY).toBe('test-key');
299
});
300
```
301
302
## Async Wait Utilities
303
304
```typescript { .api }
305
vi.waitFor<T>(
306
callback: () => T | Promise<T>,
307
options?: { timeout?: number; interval?: number }
308
): Promise<T>;
309
310
vi.waitUntil<T>(
311
callback: () => T | Promise<T>,
312
options?: { timeout?: number; interval?: number }
313
): Promise<T>;
314
```
315
316
**Difference:**
317
- `waitFor`: Retries until callback **doesn't throw**
318
- `waitUntil`: Retries until callback returns **truthy value** (fails if throws)
319
320
**Examples:**
321
```typescript
322
test('wait for condition', async () => {
323
let ready = false;
324
setTimeout(() => { ready = true }, 500);
325
326
await vi.waitUntil(
327
() => ready,
328
{ timeout: 1000, interval: 50 }
329
);
330
331
expect(ready).toBe(true);
332
});
333
334
test('wait for no errors', async () => {
335
const server = createServer();
336
337
await vi.waitFor(
338
() => {
339
if (!server.isReady) throw new Error('Not ready');
340
},
341
{ timeout: 2000, interval: 100 }
342
);
343
344
expect(server.isReady).toBe(true);
345
});
346
347
test('wait for element', async () => {
348
const element = await vi.waitUntil(
349
() => document.querySelector('.loaded'),
350
{ timeout: 1000 }
351
);
352
353
expect(element).toBeTruthy();
354
});
355
```
356
357
## Module Cache
358
359
```typescript { .api }
360
vi.resetModules(): VitestUtils;
361
vi.dynamicImportSettled(): Promise<void>;
362
```
363
364
**Examples:**
365
```typescript
366
test('reset modules', async () => {
367
const { value: value1 } = await import('./module');
368
369
vi.resetModules(); // Clear cache
370
371
const { value: value2 } = await import('./module');
372
// Fresh module instance
373
});
374
375
test('wait for dynamic imports', async () => {
376
const promises = [
377
import('./module1'),
378
import('./module2'),
379
import('./module3'),
380
];
381
382
await vi.dynamicImportSettled();
383
// All imports completed
384
});
385
```
386
387
## Common Mock Patterns
388
389
### Mock Fetch
390
391
```typescript
392
test('mock fetch', async () => {
393
const mockFetch = vi.fn(() =>
394
Promise.resolve({
395
ok: true,
396
json: () => Promise.resolve({ data: 'test' }),
397
})
398
);
399
400
vi.stubGlobal('fetch', mockFetch);
401
402
const response = await fetch('/api/data');
403
const data = await response.json();
404
405
expect(data).toEqual({ data: 'test' });
406
expect(mockFetch).toHaveBeenCalledWith('/api/data');
407
408
vi.unstubAllGlobals();
409
});
410
```
411
412
### Mock Date
413
414
```typescript
415
test('mock date', () => {
416
const mockDate = new Date('2024-01-01T00:00:00Z');
417
vi.setSystemTime(mockDate);
418
419
expect(new Date().toISOString()).toBe('2024-01-01T00:00:00.000Z');
420
421
vi.useRealTimers(); // Restore
422
});
423
```
424
425
### Partial Module Mock
426
427
```typescript
428
vi.mock('./api', async () => {
429
const actual = await vi.importActual<typeof import('./api')>('./api');
430
return {
431
...actual,
432
fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'Test' })),
433
};
434
});
435
436
test('partial mock', async () => {
437
const { fetchUser, otherFunction } = await import('./api');
438
439
expect(vi.isMockFunction(fetchUser)).toBe(true); // Mocked
440
expect(vi.isMockFunction(otherFunction)).toBe(false); // Real
441
});
442
```
443
444
### Automatic Cleanup
445
446
```typescript
447
beforeEach(() => {
448
// Setup mocks
449
});
450
451
afterEach(() => {
452
vi.clearAllMocks(); // Clear call history
453
vi.resetAllMocks(); // Reset implementations
454
vi.restoreAllMocks(); // Restore spies
455
vi.unstubAllGlobals(); // Restore globals
456
vi.unstubAllEnvs(); // Restore env vars
457
});
458
```
459
460
## Type Definitions
461
462
```typescript { .api }
463
interface MockContext<Args extends any[], R> {
464
calls: Args[];
465
results: MockResult<R>[];
466
instances: any[];
467
invocationCallOrder: number[];
468
lastCall?: Args;
469
}
470
471
interface MockResult<T> {
472
type: 'return' | 'throw';
473
value: T;
474
}
475
476
type Mocked<T> = T extends (...args: any[]) => any
477
? MockedFunction<T>
478
: T extends object
479
? MockedObject<T>
480
: T;
481
482
type MockedFunction<T extends (...args: any[]) => any> = Mock<
483
Parameters<T>,
484
ReturnType<T>
485
> & T;
486
487
type MockedObject<T> = {
488
[K in keyof T]: T[K] extends (...args: any[]) => any
489
? MockedFunction<T[K]>
490
: T[K];
491
} & T;
492
```
493
494
## Mock Strategy Guide
495
496
| Scenario | Use | Reason |
497
|----------|-----|--------|
498
| Track function calls | `vi.fn()` | Full control over implementation |
499
| Keep original behavior | `vi.spyOn()` | Monitor calls without changing behavior |
500
| Replace entire module | `vi.mock()` | Isolate from dependencies |
501
| Partial module mock | `vi.mock() + importActual()` | Mock some exports, keep others |
502
| Mock global APIs | `vi.stubGlobal()` | Replace built-in globals (fetch, etc) |
503
| Conditional mock | `vi.doMock()` | Mock only in specific tests |
504
| Share mock state | `vi.hoisted()` | Access variables in factory |
505