0
# Test Definition and Lifecycle
1
2
API for defining tests, organizing them into suites, and managing test lifecycle with hooks.
3
4
## Test Definition
5
6
```typescript { .api }
7
function test(name: string, fn: TestFunction, options?: TestOptions): void;
8
function it(name: string, fn: TestFunction, options?: TestOptions): void; // Alias
9
10
interface TestFunction {
11
(context: TestContext): void | Promise<void>;
12
}
13
14
interface TestOptions {
15
timeout?: number; // Test timeout in ms
16
retry?: number; // Retries on failure
17
concurrent?: boolean; // Run concurrently
18
repeats?: number; // Repeat test N times
19
}
20
21
interface TestContext {
22
meta: Record<string, any>; // Metadata storage
23
task: RunnerTestCase; // Test task object
24
expect: ExpectStatic; // Expect function
25
}
26
```
27
28
**Example:**
29
```typescript
30
test('basic test', () => {
31
expect(1 + 2).toBe(3);
32
});
33
34
test('async test', async () => {
35
const data = await fetchData();
36
expect(data).toBeDefined();
37
});
38
39
test('with timeout', async () => {
40
await longOperation();
41
}, { timeout: 10000 });
42
43
test('with retry', () => {
44
// Flaky test code
45
}, { retry: 3 });
46
```
47
48
## Suite Definition
49
50
```typescript { .api }
51
function describe(name: string, fn: () => void, options?: TestOptions): void;
52
function suite(name: string, fn: () => void, options?: TestOptions): void; // Alias
53
```
54
55
**Example:**
56
```typescript
57
describe('Math operations', () => {
58
test('addition', () => expect(1 + 2).toBe(3));
59
test('subtraction', () => expect(5 - 2).toBe(3));
60
61
describe('multiplication', () => {
62
test('positive', () => expect(2 * 3).toBe(6));
63
test('negative', () => expect(-2 * -3).toBe(6));
64
});
65
});
66
```
67
68
## Test Modifiers
69
70
| Modifier | Purpose | Example |
71
|----------|---------|---------|
72
| `.only` | Run only this test/suite | `test.only('runs', ...)` |
73
| `.skip` | Skip this test/suite | `test.skip('skipped', ...)` |
74
| `.todo` | Mark as not implemented | `test.todo('implement later')` |
75
| `.skipIf(cond)` | Skip if condition true | `test.skipIf(isWindows)` |
76
| `.runIf(cond)` | Run only if condition true | `test.runIf(hasEnv)` |
77
| `.concurrent` | Run concurrently | `test.concurrent('parallel', ...)` |
78
| `.sequential` | Run sequentially | `test.sequential('serial', ...)` |
79
| `.fails` | Expect test to fail | `test.fails('expected fail', ...)` |
80
| `.each(data)` | Parameterized test | `test.each([...])('test %i', ...)` |
81
82
**Examples:**
83
```typescript
84
// Run only specific tests
85
test.only('focused test', () => { /* runs */ });
86
test('other test', () => { /* skipped */ });
87
88
// Conditional execution
89
test.skipIf(process.platform === 'win32')('Unix only', () => { ... });
90
test.runIf(process.env.INTEGRATION)('integration test', () => { ... });
91
92
// Concurrent execution
93
describe.concurrent('parallel suite', () => {
94
test('test 1', async () => { /* runs in parallel */ });
95
test('test 2', async () => { /* runs in parallel */ });
96
});
97
98
// Expected failures
99
test.fails('expected to fail', () => {
100
expect(1).toBe(2); // Test passes because it fails as expected
101
});
102
103
// Parameterized tests
104
test.each([
105
[1, 1, 2],
106
[2, 2, 4],
107
[3, 3, 6]
108
])('adds %i + %i = %i', (a, b, expected) => {
109
expect(a + b).toBe(expected);
110
});
111
```
112
113
## Lifecycle Hooks
114
115
```typescript { .api }
116
function beforeAll(fn: () => void | Promise<void>, timeout?: number): void;
117
function afterAll(fn: () => void | Promise<void>, timeout?: number): void;
118
function beforeEach(fn: (context: TestContext) => void | Promise<void>, timeout?: number): void;
119
function afterEach(fn: (context: TestContext) => void | Promise<void>, timeout?: number): void;
120
```
121
122
| Hook | Runs | Use Case |
123
|------|------|----------|
124
| `beforeAll` | Once before all tests | Expensive setup (DB connection) |
125
| `afterAll` | Once after all tests | Cleanup (close connections) |
126
| `beforeEach` | Before each test | Reset state, create instances |
127
| `afterEach` | After each test | Cleanup, restore mocks |
128
129
**Example:**
130
```typescript
131
describe('Database tests', () => {
132
let db;
133
134
beforeAll(async () => {
135
db = await connectToDatabase();
136
await db.migrate();
137
});
138
139
afterAll(async () => {
140
await db.close();
141
});
142
143
beforeEach(async () => {
144
await db.clear();
145
await db.seed();
146
});
147
148
afterEach(async () => {
149
await db.rollback();
150
});
151
152
test('creates user', async () => {
153
const user = await db.createUser({ name: 'John' });
154
expect(user.id).toBeDefined();
155
});
156
157
test('finds user', async () => {
158
await db.createUser({ name: 'John' });
159
const user = await db.findUser('John');
160
expect(user).toBeDefined();
161
});
162
});
163
```
164
165
## Test Event Handlers
166
167
```typescript { .api }
168
function onTestFailed(handler: (result: RunnerTestCase, error: unknown) => void | Promise<void>): void;
169
function onTestFinished(handler: (result: RunnerTestCase) => void | Promise<void>): void;
170
```
171
172
**Example:**
173
```typescript
174
test('with event handlers', () => {
175
onTestFailed((result, error) => {
176
console.log(`Test "${result.name}" failed:`, error);
177
});
178
179
onTestFinished((result) => {
180
console.log(`Test "${result.name}" finished: ${result.result?.state}`);
181
});
182
183
expect(1).toBe(1);
184
});
185
```
186
187
## Test Modifiers API
188
189
```typescript { .api }
190
interface TestAPI {
191
only: TestAPI;
192
skip: TestAPI;
193
todo: TestAPI;
194
skipIf(condition: boolean): TestAPI;
195
runIf(condition: boolean): TestAPI;
196
concurrent: TestAPI;
197
sequential: TestAPI;
198
fails: TestAPI;
199
each<T>(cases: T[]): (name: string, fn: (...args: T[]) => void) => void;
200
}
201
202
interface SuiteAPI {
203
only: SuiteAPI;
204
skip: SuiteAPI;
205
todo: SuiteAPI;
206
skipIf(condition: boolean): SuiteAPI;
207
runIf(condition: boolean): SuiteAPI;
208
concurrent: SuiteAPI;
209
sequential: SuiteAPI;
210
}
211
```
212
213
## Execution Control Patterns
214
215
### Focus on Specific Tests
216
217
```typescript
218
describe('feature', () => {
219
test.only('this runs', () => { /* only this */ });
220
test('skipped', () => { /* skipped */ });
221
});
222
223
describe.only('focused suite', () => {
224
test('runs', () => { /* runs because suite focused */ });
225
});
226
```
227
228
### Skip Tests
229
230
```typescript
231
test.skip('not ready', () => { /* skipped */ });
232
233
describe.skip('disabled suite', () => {
234
test('also skipped', () => { /* skipped */ });
235
});
236
```
237
238
### Parallel vs Sequential
239
240
```typescript
241
// Parallel (for independent tests)
242
describe.concurrent('parallel', () => {
243
test('test 1', async () => { await delay(100); });
244
test('test 2', async () => { await delay(100); });
245
// Both run simultaneously
246
});
247
248
// Sequential (default, for tests with shared state)
249
describe.sequential('sequential', () => {
250
test('test 1', () => { /* runs first */ });
251
test('test 2', () => { /* runs second */ });
252
});
253
```
254
255
## Type Definitions
256
257
```typescript { .api }
258
interface RunnerTestCase {
259
id: string;
260
name: string;
261
mode: 'run' | 'skip' | 'only' | 'todo';
262
suite?: RunnerTestSuite;
263
result?: RunnerTaskResult;
264
meta: Record<string, any>;
265
}
266
267
interface RunnerTestSuite {
268
id: string;
269
name: string;
270
mode: 'run' | 'skip' | 'only' | 'todo';
271
tasks: (RunnerTestCase | RunnerTestSuite)[];
272
meta: Record<string, any>;
273
}
274
275
interface RunnerTaskResult {
276
state: 'pass' | 'fail' | 'skip';
277
duration?: number;
278
error?: unknown;
279
retryCount?: number;
280
}
281
282
interface TaskCustomOptions {
283
timeout?: number;
284
retry?: number;
285
repeats?: number;
286
concurrent?: boolean;
287
}
288
```
289
290
## Common Patterns
291
292
### Parameterized Tests
293
294
```typescript
295
// Array of inputs
296
test.each([
297
{ input: 'hello', expected: 5 },
298
{ input: 'world', expected: 5 },
299
{ input: 'foo', expected: 3 }
300
])('length of "$input" is $expected', ({ input, expected }) => {
301
expect(input.length).toBe(expected);
302
});
303
304
// Multiple arguments
305
test.each([
306
[1, 1, 2],
307
[1, 2, 3],
308
[2, 1, 3]
309
])('.add(%i, %i) = %i', (a, b, expected) => {
310
expect(a + b).toBe(expected);
311
});
312
```
313
314
### Conditional Tests
315
316
```typescript
317
const isCI = !!process.env.CI;
318
const isWindows = process.platform === 'win32';
319
320
test.skipIf(isWindows)('Unix-specific feature', () => { ... });
321
test.runIf(isCI)('CI-only test', () => { ... });
322
test.skipIf(!process.env.API_KEY)('needs API key', () => { ... });
323
```
324
325
### Shared Setup with Context
326
327
```typescript
328
describe('user operations', () => {
329
let user;
330
331
beforeEach(() => {
332
user = createUser();
333
});
334
335
test('updates name', () => {
336
user.name = 'John';
337
expect(user.name).toBe('John');
338
});
339
340
test('updates email', () => {
341
user.email = 'john@example.com';
342
expect(user.email).toBe('john@example.com');
343
});
344
});
345
```
346
347
## Best Practices
348
349
1. **Use `beforeEach` for test isolation** - Each test gets fresh state
350
2. **Use `beforeAll` for expensive setup** - Database connections, file reads
351
3. **Always clean up in `afterEach/afterAll`** - Prevent test pollution
352
4. **Use `.only` during debugging** - Focus on specific failing tests
353
5. **Use `.concurrent` for independent tests** - Faster test execution
354
6. **Use `.sequential` for shared state** - Tests that modify shared resources
355
7. **Use `.each` for similar tests** - Reduce code duplication
356
357
## Lifecycle Order
358
359
```
360
beforeAll (suite)
361
├─ beforeEach (test 1)
362
│ ├─ test 1
363
│ └─ afterEach (test 1)
364
├─ beforeEach (test 2)
365
│ ├─ test 2
366
│ └─ afterEach (test 2)
367
└─ afterAll (suite)
368
```
369
370
Nested suites:
371
```
372
beforeAll (outer)
373
├─ beforeEach (outer)
374
│ ├─ beforeAll (inner)
375
│ │ ├─ beforeEach (inner)
376
│ │ │ ├─ test
377
│ │ │ └─ afterEach (inner)
378
│ │ └─ afterAll (inner)
379
│ └─ afterEach (outer)
380
└─ afterAll (outer)
381
```
382