0
# Mocking and Spying
1
2
Mock functions and spies with reactive instrumentation for Storybook integration. All mock functions are automatically tracked and can be debugged in the addon-interactions panel, with automatic cleanup between stories.
3
4
## Capabilities
5
6
### Mock Functions
7
8
Create mock functions that track calls and can be configured with custom implementations.
9
10
```typescript { .api }
11
/**
12
* Create a mock function with optional implementation
13
* @param implementation - Optional function implementation
14
* @returns Mock function with tracking capabilities
15
*/
16
function fn<T extends Procedure = Procedure>(implementation?: T): Mock<T>;
17
18
// Legacy overloads (deprecated in v9.0)
19
function fn<TArgs extends any[] = any, R = any>(): Mock<(...args: TArgs) => R>;
20
function fn<TArgs extends any[] = any[], R = any>(
21
implementation: (...args: TArgs) => R
22
): Mock<(...args: TArgs) => R>;
23
24
type Procedure = (...args: any[]) => any;
25
26
type Mock<T extends Procedure | any[] = any[], R = any> = T extends Procedure
27
? MockV2<T>
28
: T extends any[]
29
? MockV2<(...args: T) => R>
30
: never;
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
import { fn, expect } from '@storybook/test';
37
38
// Create a basic mock
39
const mockCallback = fn();
40
mockCallback('arg1', 'arg2');
41
expect(mockCallback).toHaveBeenCalledWith('arg1', 'arg2');
42
43
// Create a mock with implementation
44
const mockAdd = fn((a: number, b: number) => a + b);
45
const result = mockAdd(2, 3);
46
expect(result).toBe(5);
47
expect(mockAdd).toHaveBeenCalledWith(2, 3);
48
49
// Mock with custom name for debugging
50
const namedMock = fn().mockName('customName');
51
expect(namedMock.getMockName()).toBe('customName');
52
```
53
54
### Spying on Objects
55
56
Create spies on existing object methods while preserving original behavior.
57
58
```typescript { .api }
59
/**
60
* Create a spy on an object method
61
* @param object - Target object
62
* @param method - Method name to spy on
63
* @returns Mock instance that wraps the original method
64
*/
65
function spyOn<T, K extends keyof T>(
66
object: T,
67
method: K
68
): T[K] extends (...args: any[]) => any ? MockInstance<Parameters<T[K]>, ReturnType<T[K]>> : never;
69
```
70
71
**Usage Examples:**
72
73
```typescript
74
import { spyOn, expect } from '@storybook/test';
75
76
class Calculator {
77
add(a: number, b: number) {
78
return a + b;
79
}
80
}
81
82
const calc = new Calculator();
83
84
export const SpyStory = {
85
play: async () => {
86
// Spy on the method while preserving original behavior
87
const addSpy = spyOn(calc, 'add');
88
89
const result = calc.add(2, 3);
90
expect(result).toBe(5); // Original behavior preserved
91
expect(addSpy).toHaveBeenCalledWith(2, 3);
92
93
// Override implementation
94
addSpy.mockImplementation((a, b) => a * b);
95
const multiplied = calc.add(2, 3);
96
expect(multiplied).toBe(6); // Now it multiplies
97
98
// Restore original behavior
99
addSpy.mockRestore();
100
const restored = calc.add(2, 3);
101
expect(restored).toBe(5); // Back to addition
102
},
103
};
104
```
105
106
### Mock Instance Interface
107
108
All mock functions implement the MockInstance interface with comprehensive tracking and configuration methods.
109
110
```typescript { .api }
111
interface MockInstance<TArgs extends any[] = any[], TReturns = any> {
112
/**
113
* Get the current mock name for debugging
114
*/
115
getMockName(): string;
116
117
/**
118
* Set a name for the mock for debugging purposes
119
* @param name - Name to assign to the mock
120
*/
121
mockName(name: string): this;
122
123
/**
124
* Clear all call history but preserve implementation
125
*/
126
mockClear(): this;
127
128
/**
129
* Reset mock to initial state (clear calls and implementation)
130
*/
131
mockReset(): this;
132
133
/**
134
* Restore original implementation (for spies)
135
*/
136
mockRestore(): void;
137
138
/**
139
* Set a custom implementation for the mock
140
* @param fn - Implementation function
141
*/
142
mockImplementation(fn?: (...args: TArgs) => TReturns): this;
143
144
/**
145
* Set implementation for the next single call only
146
* @param fn - Implementation function for one call
147
*/
148
mockImplementationOnce(fn: (...args: TArgs) => TReturns): this;
149
150
/**
151
* Set return value for all calls
152
* @param value - Value to return
153
*/
154
mockReturnValue(value: TReturns): this;
155
156
/**
157
* Set return value for the next single call only
158
* @param value - Value to return for one call
159
*/
160
mockReturnValueOnce(value: TReturns): this;
161
162
/**
163
* Set resolved value for async mocks
164
* @param value - Value to resolve with
165
*/
166
mockResolvedValue(value: Awaited<TReturns>): this;
167
168
/**
169
* Set resolved value for the next single call only
170
* @param value - Value to resolve with for one call
171
*/
172
mockResolvedValueOnce(value: Awaited<TReturns>): this;
173
174
/**
175
* Set rejected value for async mocks
176
* @param value - Value to reject with
177
*/
178
mockRejectedValue(value: any): this;
179
180
/**
181
* Set rejected value for the next single call only
182
* @param value - Value to reject with for one call
183
*/
184
mockRejectedValueOnce(value: any): this;
185
186
// Call tracking properties
187
mock: {
188
calls: TArgs[];
189
results: Array<{ type: 'return' | 'throw'; value: TReturns }>;
190
instances: any[];
191
contexts: any[];
192
lastCall?: TArgs;
193
};
194
}
195
```
196
197
**Usage Examples:**
198
199
```typescript
200
import { fn, expect } from '@storybook/test';
201
202
export const MockInstanceStory = {
203
play: async () => {
204
const mock = fn<[number, number], number>();
205
206
// Configure return values
207
mock.mockReturnValue(42);
208
expect(mock(1, 2)).toBe(42);
209
210
mock.mockReturnValueOnce(100);
211
expect(mock(3, 4)).toBe(100); // One-time return
212
expect(mock(5, 6)).toBe(42); // Back to default
213
214
// Configure implementation
215
mock.mockImplementation((a, b) => a + b);
216
expect(mock(10, 20)).toBe(30);
217
218
// One-time implementation
219
mock.mockImplementationOnce((a, b) => a * b);
220
expect(mock(3, 4)).toBe(12); // Multiply once
221
expect(mock(3, 4)).toBe(7); // Back to addition
222
223
// Async mocks
224
const asyncMock = fn<[], Promise<string>>();
225
asyncMock.mockResolvedValue('success');
226
await expect(asyncMock()).resolves.toBe('success');
227
228
asyncMock.mockRejectedValueOnce('error');
229
await expect(asyncMock()).rejects.toBe('error');
230
await expect(asyncMock()).resolves.toBe('success');
231
},
232
};
233
```
234
235
### Mock Lifecycle Management
236
237
Utilities for managing mocks across stories and test scenarios.
238
239
```typescript { .api }
240
/**
241
* Clear call history for all active mocks
242
* Calls .mockClear() on every mock
243
*/
244
function clearAllMocks(): void;
245
246
/**
247
* Reset all active mocks to initial state
248
* Calls .mockReset() on every mock
249
*/
250
function resetAllMocks(): void;
251
252
/**
253
* Restore all spied methods to original implementations
254
* Calls .mockRestore() on every mock
255
*/
256
function restoreAllMocks(): void;
257
258
/**
259
* Check if a value is a mock function
260
* @param value - Value to check
261
* @returns True if value is a mock function
262
*/
263
function isMockFunction(value: unknown): boolean;
264
265
/**
266
* Set of all active mock instances
267
*/
268
const mocks: Set<MockInstance>;
269
```
270
271
**Usage Examples:**
272
273
```typescript
274
import { fn, spyOn, clearAllMocks, resetAllMocks, restoreAllMocks, isMockFunction } from '@storybook/test';
275
276
const originalConsole = console.log;
277
278
export const LifecycleStory = {
279
play: async () => {
280
// Create various mocks
281
const mockFn = fn();
282
const consoleSpy = spyOn(console, 'log');
283
284
mockFn('test');
285
console.log('test message');
286
287
// Check if something is a mock
288
expect(isMockFunction(mockFn)).toBe(true);
289
expect(isMockFunction(console.log)).toBe(true);
290
expect(isMockFunction(originalConsole)).toBe(false);
291
292
// Clear all call history
293
clearAllMocks();
294
expect(mockFn).not.toHaveBeenCalled();
295
expect(consoleSpy).not.toHaveBeenCalled();
296
297
// Reset all mocks (clears history and implementations)
298
mockFn.mockReturnValue('custom');
299
resetAllMocks();
300
expect(mockFn()).toBeUndefined(); // Implementation reset
301
302
// Restore original implementations
303
restoreAllMocks();
304
console.log('this works normally again');
305
},
306
};
307
```
308
309
### Mock Call Listeners
310
311
Listen to mock function calls for advanced debugging and interaction tracking.
312
313
```typescript { .api }
314
/**
315
* Register a listener for all mock function calls
316
* @param callback - Function called when any mock is invoked
317
* @returns Cleanup function to remove the listener
318
*/
319
function onMockCall(callback: Listener): () => void;
320
321
type Listener = (mock: MockInstance, args: unknown[]) => void;
322
```
323
324
**Usage Examples:**
325
326
```typescript
327
import { fn, onMockCall } from '@storybook/test';
328
329
export const ListenerStory = {
330
play: async () => {
331
const calls: Array<{ mock: MockInstance; args: unknown[] }> = [];
332
333
// Listen to all mock calls
334
const unsubscribe = onMockCall((mock, args) => {
335
calls.push({ mock, args });
336
console.log(`Mock ${mock.getMockName()} called with:`, args);
337
});
338
339
const mockA = fn().mockName('mockA');
340
const mockB = fn().mockName('mockB');
341
342
mockA('arg1');
343
mockB('arg2', 'arg3');
344
345
expect(calls).toHaveLength(2);
346
expect(calls[0].args).toEqual(['arg1']);
347
expect(calls[1].args).toEqual(['arg2', 'arg3']);
348
349
// Clean up listener
350
unsubscribe();
351
352
mockA('this will not be logged');
353
expect(calls).toHaveLength(2); // No new calls logged
354
},
355
};
356
```
357
358
### Type Helpers for Mocking
359
360
Utility types for working with mocked objects and maintaining type safety.
361
362
```typescript { .api }
363
/**
364
* Type helper for objects that may have mocked methods
365
* @param item - Object to type as potentially mocked
366
* @param deep - Whether to deeply mock nested objects (default: false)
367
* @param options - Configuration for mocking behavior
368
* @returns The same object with mock typing
369
*/
370
function mocked<T>(item: T, deep?: false): MaybeMocked<T>;
371
function mocked<T>(item: T, deep: true): MaybeMockedDeep<T>;
372
function mocked<T>(item: T, options: { partial?: false; deep?: false }): MaybeMocked<T>;
373
function mocked<T>(item: T, options: { partial?: false; deep: true }): MaybeMockedDeep<T>;
374
function mocked<T>(item: T, options: { partial: true; deep?: false }): MaybePartiallyMocked<T>;
375
function mocked<T>(item: T, options: { partial: true; deep: true }): MaybePartiallyMockedDeep<T>;
376
377
type MaybeMocked<T> = T & {
378
[K in keyof T]: T[K] extends (...args: any[]) => any
379
? MockInstance<Parameters<T[K]>, ReturnType<T[K]>>
380
: T[K];
381
};
382
383
type MaybeMockedDeep<T> = T & {
384
[K in keyof T]: T[K] extends (...args: any[]) => any
385
? MockInstance<Parameters<T[K]>, ReturnType<T[K]>>
386
: MaybeMockedDeep<T[K]>;
387
};
388
389
type MaybePartiallyMocked<T> = {
390
[K in keyof T]?: T[K] extends (...args: any[]) => any
391
? MockInstance<Parameters<T[K]>, ReturnType<T[K]>>
392
: T[K];
393
};
394
395
type MaybePartiallyMockedDeep<T> = {
396
[K in keyof T]?: T[K] extends (...args: any[]) => any
397
? MockInstance<Parameters<T[K]>, ReturnType<T[K]>>
398
: MaybePartiallyMockedDeep<T[K]>;
399
};
400
```
401
402
**Usage Examples:**
403
404
```typescript
405
import { mocked, spyOn } from '@storybook/test';
406
407
interface ApiService {
408
fetchUser(id: string): Promise<User>;
409
updateUser(user: User): Promise<void>;
410
nested: {
411
helper(data: any): string;
412
};
413
}
414
415
export const TypeHelpersStory = {
416
play: async () => {
417
const apiService: ApiService = {
418
fetchUser: async (id) => ({ id, name: 'John' }),
419
updateUser: async (user) => {},
420
nested: {
421
helper: (data) => JSON.stringify(data),
422
},
423
};
424
425
// Spy on methods and use type helper
426
spyOn(apiService, 'fetchUser');
427
spyOn(apiService, 'updateUser');
428
429
// Type helper provides correct typing for mocked methods
430
const mockedService = mocked(apiService);
431
mockedService.fetchUser.mockResolvedValue({ id: '1', name: 'Mock User' });
432
433
const user = await apiService.fetchUser('1');
434
expect(user.name).toBe('Mock User');
435
436
// Deep mocking for nested objects
437
const deepMocked = mocked(apiService, { deep: true });
438
// Now deepMocked.nested.helper is also typed as a mock
439
},
440
};
441
```