0
# Testing and Mocking
1
2
Comprehensive testing utilities built on top of popular testing libraries with Storybook-specific instrumentation and integrations. Provides enhanced expect functionality, user interaction utilities, and module mocking capabilities.
3
4
## Capabilities
5
6
### Enhanced Expect
7
8
Storybook's expect is based on Chai with additional instrumentation for better integration with Storybook's testing ecosystem.
9
10
```typescript { .api }
11
/**
12
* Enhanced expect function with Storybook instrumentation
13
* Provides assertion capabilities with detailed error reporting
14
*/
15
const expect: Expect;
16
17
interface Expect {
18
<T>(actual: T): ExpectStatic<T>;
19
/** Check if value is null */
20
(actual: null): ExpectStatic<null>;
21
/** Check if value is undefined */
22
(actual: undefined): ExpectStatic<undefined>;
23
/** Use with spy assertions */
24
<T extends (...args: any[]) => any>(actual: T): ExpectStatic<T>;
25
}
26
27
interface ExpectStatic<T> {
28
/** Negates the assertion */
29
not: ExpectStatic<T>;
30
/** Basic equality assertion */
31
toBe(expected: T): void;
32
/** Deep equality assertion */
33
toEqual(expected: T): void;
34
/** Truthiness assertion */
35
toBeTruthy(): void;
36
/** Falsiness assertion */
37
toBeFalsy(): void;
38
/** Null assertion */
39
toBeNull(): void;
40
/** Undefined assertion */
41
toBeUndefined(): void;
42
/** Array/string contains assertion */
43
toContain(expected: any): void;
44
/** Function call assertion */
45
toHaveBeenCalled(): void;
46
/** Function call count assertion */
47
toHaveBeenCalledTimes(times: number): void;
48
/** Function call arguments assertion */
49
toHaveBeenCalledWith(...args: any[]): void;
50
// ... additional matchers
51
}
52
```
53
54
**Usage Example:**
55
56
```typescript
57
import { expect, userEvent, within } from "storybook/test";
58
import { composeStory } from "storybook/preview-api";
59
import { Primary } from "./Button.stories";
60
61
const ComposedButton = composeStory(Primary, ButtonMeta);
62
63
test("button interaction", async () => {
64
const onClickSpy = vi.fn();
65
render(<ComposedButton onClick={onClickSpy} />);
66
67
const button = screen.getByRole("button");
68
await userEvent.click(button);
69
70
expect(onClickSpy).toHaveBeenCalledOnce();
71
expect(button).toBeInTheDocument();
72
});
73
```
74
75
### User Event Utilities
76
77
User interaction testing utilities for simulating user actions with proper async handling and event propagation.
78
79
```typescript { .api }
80
/**
81
* User event utilities for simulating user interactions
82
* Provides type-safe, async user interaction methods
83
*/
84
const userEvent: UserEvent;
85
86
interface UserEvent {
87
/** Setup user event with custom configuration */
88
setup(options?: UserEventOptions): UserEventObject;
89
/** Click an element */
90
click(element: Element, options?: ClickOptions): Promise<void>;
91
/** Double click an element */
92
dblClick(element: Element, options?: ClickOptions): Promise<void>;
93
/** Type text into an element */
94
type(element: Element, text: string, options?: TypeOptions): Promise<void>;
95
/** Clear and type text */
96
clear(element: Element): Promise<void>;
97
/** Select text in an input */
98
selectAll(element: Element): Promise<void>;
99
/** Tab to next/previous element */
100
tab(options?: TabOptions): Promise<void>;
101
/** Hover over an element */
102
hover(element: Element): Promise<void>;
103
/** Stop hovering over an element */
104
unhover(element: Element): Promise<void>;
105
/** Upload files to file input */
106
upload(element: Element, files: File | File[]): Promise<void>;
107
}
108
109
interface UserEventOptions {
110
/** Delay between events in milliseconds */
111
delay?: number;
112
/** Skip pointer events */
113
skipPointerEventsCheck?: boolean;
114
/** Skip hover events */
115
skipHover?: boolean;
116
}
117
118
interface ClickOptions {
119
button?: 'left' | 'right' | 'middle';
120
ctrlKey?: boolean;
121
shiftKey?: boolean;
122
altKey?: boolean;
123
metaKey?: boolean;
124
}
125
126
interface TypeOptions {
127
delay?: number;
128
skipClick?: boolean;
129
initialSelectionStart?: number;
130
initialSelectionEnd?: number;
131
}
132
```
133
134
**Usage Example:**
135
136
```typescript
137
import { userEvent, within, expect } from "storybook/test";
138
139
export const InteractiveForm: Story = {
140
play: async ({ canvasElement }) => {
141
const canvas = within(canvasElement);
142
143
// Type in form fields
144
await userEvent.type(canvas.getByLabelText(/name/i), "John Doe");
145
await userEvent.type(canvas.getByLabelText(/email/i), "john@example.com");
146
147
// Select dropdown option
148
await userEvent.selectOptions(
149
canvas.getByLabelText(/country/i),
150
["usa"]
151
);
152
153
// Upload file
154
const file = new File(["avatar"], "avatar.png", { type: "image/png" });
155
await userEvent.upload(canvas.getByLabelText(/avatar/i), file);
156
157
// Submit form
158
await userEvent.click(canvas.getByRole("button", { name: /submit/i }));
159
160
// Verify results
161
expect(canvas.getByText(/success/i)).toBeInTheDocument();
162
},
163
};
164
```
165
166
### Module Mocking
167
168
Module mocking utilities for replacing imports and dependencies in tests and stories.
169
170
```typescript { .api }
171
/**
172
* Storybook's module mocking utilities
173
*/
174
interface MockUtilities {
175
/**
176
* Mock a module with a factory function or mock implementation
177
* @param path - Module path to mock (string or Promise for dynamic imports)
178
* @param factory - Optional factory function to create mock implementation
179
*/
180
mock(path: string | Promise<unknown>, factory?: ModuleMockOptions): void;
181
}
182
183
const sb: MockUtilities;
184
185
type ModuleMockOptions =
186
| (() => any)
187
| { [key: string]: any }
188
| any;
189
```
190
191
**Usage Example:**
192
193
```typescript
194
import { sb } from "storybook/test";
195
196
// Mock an API module
197
sb.mock("./api/userService", () => ({
198
fetchUser: vi.fn().mockResolvedValue({
199
id: 1,
200
name: "John Doe",
201
email: "john@example.com"
202
}),
203
updateUser: vi.fn().mockResolvedValue({ success: true }),
204
}));
205
206
// Mock with partial implementation
207
sb.mock("./utils/analytics", () => ({
208
track: vi.fn(),
209
identify: vi.fn(),
210
// Keep other exports as default
211
...vi.importActual("./utils/analytics"),
212
}));
213
214
// Use in story
215
export const ComponentWithMockedAPI: Story = {
216
play: async ({ canvasElement }) => {
217
const canvas = within(canvasElement);
218
219
// Component will use mocked userService
220
await userEvent.click(canvas.getByText("Load User"));
221
222
// Verify mock was called
223
const { fetchUser } = await import("./api/userService");
224
expect(fetchUser).toHaveBeenCalledWith(1);
225
},
226
};
227
```
228
229
## Testing Library Integration
230
231
Complete instrumented re-export of `@testing-library/dom` with Storybook-specific enhancements.
232
233
### Query Functions
234
235
```typescript { .api }
236
// Get* queries - throw error if not found
237
function getByRole(container: Element, role: string, options?: ByRoleOptions): HTMLElement;
238
function getByLabelText(container: Element, text: string, options?: SelectorMatcherOptions): HTMLElement;
239
function getByText(container: Element, text: string, options?: SelectorMatcherOptions): HTMLElement;
240
function getByDisplayValue(container: Element, value: string, options?: SelectorMatcherOptions): HTMLElement;
241
function getByAltText(container: Element, text: string, options?: SelectorMatcherOptions): HTMLElement;
242
function getByTitle(container: Element, text: string, options?: SelectorMatcherOptions): HTMLElement;
243
function getByTestId(container: Element, testId: string, options?: SelectorMatcherOptions): HTMLElement;
244
245
// GetAll* queries - return array, throw if none found
246
function getAllByRole(container: Element, role: string, options?: ByRoleOptions): HTMLElement[];
247
// ... similar pattern for other queries
248
249
// Query* queries - return null if not found
250
function queryByRole(container: Element, role: string, options?: ByRoleOptions): HTMLElement | null;
251
// ... similar pattern for other queries
252
253
// QueryAll* queries - return empty array if none found
254
function queryAllByRole(container: Element, role: string, options?: ByRoleOptions): HTMLElement[];
255
// ... similar pattern for other queries
256
257
// Find* queries - return promise, reject if not found after timeout
258
function findByRole(container: Element, role: string, options?: ByRoleOptions): Promise<HTMLElement>;
259
// ... similar pattern for other queries
260
261
// FindAll* queries - return promise of array
262
function findAllByRole(container: Element, role: string, options?: ByRoleOptions): Promise<HTMLElement[]>;
263
// ... similar pattern for other queries
264
```
265
266
### Event Utilities
267
268
```typescript { .api }
269
/**
270
* Fire DOM events on elements
271
*/
272
const fireEvent: {
273
[K in keyof HTMLElementEventMap]: (
274
element: Element,
275
eventProperties?: Partial<HTMLElementEventMap[K]>
276
) => boolean;
277
} & {
278
/** Create a DOM event */
279
createEvent: (eventName: string, node?: Element, init?: object) => Event;
280
};
281
```
282
283
### Async Utilities
284
285
```typescript { .api }
286
/**
287
* Wait for element to appear or condition to be met
288
* @param callback - Function to execute and wait for
289
* @param options - Configuration options
290
*/
291
function waitFor<T>(
292
callback: () => T | Promise<T>,
293
options?: WaitForOptions
294
): Promise<T>;
295
296
/**
297
* Wait for element to be removed from DOM
298
* @param element - Element to wait for removal
299
* @param options - Configuration options
300
*/
301
function waitForElementToBeRemoved<T>(
302
element: T,
303
options?: WaitForOptions
304
): Promise<void>;
305
306
interface WaitForOptions {
307
timeout?: number;
308
interval?: number;
309
onTimeout?: (error: Error) => Error;
310
}
311
```
312
313
### Screen and Within
314
315
```typescript { .api }
316
/**
317
* Global screen object for querying document
318
*/
319
const screen: Screen;
320
321
interface Screen {
322
getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
323
getAllByRole: (role: string, options?: ByRoleOptions) => HTMLElement[];
324
queryByRole: (role: string, options?: ByRoleOptions) => HTMLElement | null;
325
// ... all query methods available on screen
326
}
327
328
/**
329
* Scope queries to a specific container element
330
* @param element - Container element to scope queries to
331
* @returns Object with all query methods scoped to the container
332
*/
333
function within(element: Element): Screen;
334
```
335
336
**Usage Example:**
337
338
```typescript
339
import { screen, within, waitFor, fireEvent } from "storybook/test";
340
341
export const AsyncComponent: Story = {
342
play: async ({ canvasElement }) => {
343
const canvas = within(canvasElement);
344
345
// Click button to trigger async action
346
fireEvent.click(canvas.getByRole("button", { name: /load data/i }));
347
348
// Wait for loading to complete
349
await waitFor(() => {
350
expect(canvas.queryByText(/loading/i)).not.toBeInTheDocument();
351
});
352
353
// Verify data loaded
354
expect(canvas.getByText(/data loaded successfully/i)).toBeInTheDocument();
355
},
356
};
357
```
358
359
## Configuration and Setup
360
361
### Test Parameters
362
363
Configure testing behavior at story or project level.
364
365
```typescript { .api }
366
interface TestParameters {
367
/** Disable testing for this story */
368
disable?: boolean;
369
/** Custom test timeout */
370
timeout?: number;
371
/** Skip certain test assertions */
372
skip?: string[];
373
}
374
```
375
376
**Usage Example:**
377
378
```typescript
379
export const LongRunningTest: Story = {
380
parameters: {
381
test: {
382
timeout: 10000, // 10 second timeout
383
},
384
},
385
play: async ({ canvasElement }) => {
386
// Long-running test logic
387
},
388
};
389
```