0
# Event System
1
2
Event-driven architecture for handling test lifecycle events and integrating with custom test environments. The event system provides comprehensive hooks into test execution phases for monitoring, reporting, and custom behavior.
3
4
## Capabilities
5
6
### Event Handler Types
7
8
Event handlers are functions that respond to test lifecycle events. Note that `addEventHandler` is not part of the public API - event handling is typically done through custom test environments.
9
10
```typescript { .api }
11
interface EventHandler {
12
/** Handle async events (most test lifecycle events) */
13
(event: AsyncEvent, state: State): void | Promise<void>;
14
/** Handle sync events (definition and error events) */
15
(event: SyncEvent, state: State): void;
16
}
17
```
18
19
### Event Integration with Test Environments
20
21
Custom test environments can handle events by implementing the `handleTestEvent` method.
22
23
```typescript { .api }
24
/**
25
* Custom test environment with event handling
26
*/
27
abstract class TestEnvironment {
28
/**
29
* Handle test events
30
* @param event - The event that occurred
31
* @param state - Current test state
32
* @returns Promise that resolves when handling is complete
33
*/
34
abstract handleTestEvent(event: Event, state: State): Promise<void>;
35
}
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import { TestEnvironment as NodeEnvironment } from 'jest-environment-node';
42
import { Event, State } from 'jest-circus';
43
44
class CustomTestEnvironment extends NodeEnvironment {
45
private testStartTime: number = 0;
46
47
async handleTestEvent(event: Event, state: State): Promise<void> {
48
switch (event.name) {
49
case 'setup':
50
console.log('Test suite setup');
51
break;
52
53
case 'test_start':
54
this.testStartTime = Date.now();
55
console.log(`Starting: ${event.test.name}`);
56
break;
57
58
case 'test_done':
59
const duration = Date.now() - this.testStartTime;
60
const status = event.test.errors.length > 0 ? 'FAILED' : 'PASSED';
61
console.log(`${status}: ${event.test.name} (${duration}ms)`);
62
63
// Custom logic for failed tests
64
if (event.test.errors.length > 0) {
65
await this.handleTestFailure(event.test);
66
}
67
break;
68
69
case 'run_finish':
70
await this.generateReport(state);
71
break;
72
}
73
}
74
75
private async handleTestFailure(test: TestEntry): Promise<void> {
76
// Custom failure handling
77
await this.captureScreenshot(test.name);
78
await this.logSystemState();
79
}
80
81
private async generateReport(state: State): Promise<void> {
82
// Generate custom test report
83
const report = {
84
totalTests: this.countTests(state.rootDescribeBlock),
85
unhandledErrors: state.unhandledErrors.length,
86
timestamp: new Date().toISOString()
87
};
88
await this.saveReport(report);
89
}
90
}
91
92
module.exports = CustomTestEnvironment;
93
```
94
95
## Event Types
96
97
### Synchronous Events
98
99
Events that are dispatched synchronously and do not pause test execution.
100
101
```typescript { .api }
102
type SyncEvent =
103
| StartDescribeDefinitionEvent
104
| FinishDescribeDefinitionEvent
105
| AddHookEvent
106
| AddTestEvent
107
| ErrorEvent;
108
109
interface StartDescribeDefinitionEvent {
110
name: 'start_describe_definition';
111
blockName: BlockName;
112
mode: BlockMode;
113
asyncError: Error;
114
}
115
116
interface FinishDescribeDefinitionEvent {
117
name: 'finish_describe_definition';
118
blockName: BlockName;
119
mode: BlockMode;
120
}
121
122
interface AddHookEvent {
123
name: 'add_hook';
124
hookType: HookType;
125
fn: HookFn;
126
timeout: number | undefined;
127
asyncError: Error;
128
}
129
130
interface AddTestEvent {
131
name: 'add_test';
132
testName: TestName;
133
fn: TestFn;
134
mode?: TestMode;
135
concurrent: boolean;
136
timeout: number | undefined;
137
failing: boolean;
138
asyncError: Error;
139
}
140
141
interface ErrorEvent {
142
name: 'error';
143
error: Exception;
144
}
145
```
146
147
### Asynchronous Events
148
149
Events that are dispatched asynchronously and pause test execution until all handlers complete.
150
151
```typescript { .api }
152
type AsyncEvent =
153
| SetupEvent
154
| IncludeTestLocationEvent
155
| RunStartEvent
156
| RunFinishEvent
157
| RunDescribeStartEvent
158
| RunDescribeFinishEvent
159
| TestStartEvent
160
| TestDoneEvent
161
| TestRetryEvent
162
| HookStartEvent
163
| HookSuccessEvent
164
| HookFailureEvent;
165
166
interface SetupEvent {
167
name: 'setup';
168
testNamePattern?: string;
169
runtimeGlobals: JestGlobals;
170
parentProcess: NodeJS.Process;
171
}
172
173
interface IncludeTestLocationEvent {
174
name: 'include_test_location_in_result';
175
}
176
177
interface RunStartEvent {
178
name: 'run_start';
179
}
180
181
interface RunFinishEvent {
182
name: 'run_finish';
183
}
184
185
interface RunDescribeStartEvent {
186
name: 'run_describe_start';
187
describeBlock: DescribeBlock;
188
}
189
190
interface RunDescribeFinishEvent {
191
name: 'run_describe_finish';
192
describeBlock: DescribeBlock;
193
}
194
195
interface TestStartEvent {
196
name: 'test_start';
197
test: TestEntry;
198
}
199
200
interface TestDoneEvent {
201
name: 'test_done';
202
test: TestEntry;
203
}
204
205
interface TestRetryEvent {
206
name: 'test_retry';
207
test: TestEntry;
208
}
209
210
interface HookStartEvent {
211
name: 'hook_start';
212
hook: Hook;
213
}
214
215
interface HookSuccessEvent {
216
name: 'hook_success';
217
hook: Hook;
218
describeBlock?: DescribeBlock;
219
test?: TestEntry;
220
}
221
222
interface HookFailureEvent {
223
name: 'hook_failure';
224
error: Exception;
225
hook: Hook;
226
describeBlock?: DescribeBlock;
227
test?: TestEntry;
228
}
229
```
230
231
**Usage Examples:**
232
233
```typescript
234
import { addEventHandler } from "jest-circus";
235
236
// Handle specific event types
237
addEventHandler((event, state) => {
238
// TypeScript type narrowing
239
if (event.name === 'test_start') {
240
// event is now typed as TestStartEvent
241
console.log(`Test started: ${event.test.name}`);
242
console.log(`Parent describe: ${event.test.parent.name}`);
243
}
244
245
if (event.name === 'hook_failure') {
246
// event is now typed as HookFailureEvent
247
console.error(`${event.hook.type} hook failed:`, event.error);
248
if (event.test) {
249
console.error(`In test: ${event.test.name}`);
250
}
251
}
252
});
253
254
// Handle all test lifecycle events
255
addEventHandler(async (event, state) => {
256
const testEvents = [
257
'test_start',
258
'test_done',
259
'test_retry'
260
] as const;
261
262
if (testEvents.includes(event.name as any)) {
263
await logTestEvent(event, state);
264
}
265
});
266
```
267
268
## Event Timing and Execution
269
270
Important notes about event handling behavior:
271
272
- **Synchronous events** (`start_describe_definition`, `finish_describe_definition`, `add_hook`, `add_test`, `error`) do not pause test execution
273
- **Asynchronous events** pause execution until all handlers complete their promises
274
- Event handlers should be registered before test execution begins
275
- Multiple handlers can be registered and will execute in registration order
276
- Throwing errors in event handlers will stop test execution
277
- Event data should not be mutated as it may cause unexpected behavior
278
279
**Usage Examples:**
280
281
```typescript
282
import { addEventHandler } from "jest-circus";
283
284
// Event handler that doesn't block
285
addEventHandler((event, state) => {
286
// Synchronous logging - won't pause tests
287
if (event.name === 'add_test') {
288
console.log(`Registered test: ${event.testName}`);
289
}
290
});
291
292
// Event handler that blocks until complete
293
addEventHandler(async (event, state) => {
294
if (event.name === 'test_done') {
295
// This will pause until the async operation completes
296
await uploadTestResults(event.test);
297
}
298
});
299
300
// Error handling in event handlers
301
addEventHandler(async (event, state) => {
302
try {
303
if (event.name === 'run_finish') {
304
await generateReport(state);
305
}
306
} catch (error) {
307
console.error('Report generation failed:', error);
308
// Don't re-throw - would stop test execution
309
}
310
});
311
```