The next-gen flux-based test runner for Jest that provides test framework globals and event-driven test execution
82
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.
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.
interface EventHandler {
/** Handle async events (most test lifecycle events) */
(event: AsyncEvent, state: State): void | Promise<void>;
/** Handle sync events (definition and error events) */
(event: SyncEvent, state: State): void;
}Custom test environments can handle events by implementing the handleTestEvent method.
/**
* Custom test environment with event handling
*/
abstract class TestEnvironment {
/**
* Handle test events
* @param event - The event that occurred
* @param state - Current test state
* @returns Promise that resolves when handling is complete
*/
abstract handleTestEvent(event: Event, state: State): Promise<void>;
}Usage Examples:
import { TestEnvironment as NodeEnvironment } from 'jest-environment-node';
import { Event, State } from 'jest-circus';
class CustomTestEnvironment extends NodeEnvironment {
private testStartTime: number = 0;
async handleTestEvent(event: Event, state: State): Promise<void> {
switch (event.name) {
case 'setup':
console.log('Test suite setup');
break;
case 'test_start':
this.testStartTime = Date.now();
console.log(`Starting: ${event.test.name}`);
break;
case 'test_done':
const duration = Date.now() - this.testStartTime;
const status = event.test.errors.length > 0 ? 'FAILED' : 'PASSED';
console.log(`${status}: ${event.test.name} (${duration}ms)`);
// Custom logic for failed tests
if (event.test.errors.length > 0) {
await this.handleTestFailure(event.test);
}
break;
case 'run_finish':
await this.generateReport(state);
break;
}
}
private async handleTestFailure(test: TestEntry): Promise<void> {
// Custom failure handling
await this.captureScreenshot(test.name);
await this.logSystemState();
}
private async generateReport(state: State): Promise<void> {
// Generate custom test report
const report = {
totalTests: this.countTests(state.rootDescribeBlock),
unhandledErrors: state.unhandledErrors.length,
timestamp: new Date().toISOString()
};
await this.saveReport(report);
}
}
module.exports = CustomTestEnvironment;Events that are dispatched synchronously and do not pause test execution.
type SyncEvent =
| StartDescribeDefinitionEvent
| FinishDescribeDefinitionEvent
| AddHookEvent
| AddTestEvent
| ErrorEvent;
interface StartDescribeDefinitionEvent {
name: 'start_describe_definition';
blockName: BlockName;
mode: BlockMode;
asyncError: Error;
}
interface FinishDescribeDefinitionEvent {
name: 'finish_describe_definition';
blockName: BlockName;
mode: BlockMode;
}
interface AddHookEvent {
name: 'add_hook';
hookType: HookType;
fn: HookFn;
timeout: number | undefined;
asyncError: Error;
}
interface AddTestEvent {
name: 'add_test';
testName: TestName;
fn: TestFn;
mode?: TestMode;
concurrent: boolean;
timeout: number | undefined;
failing: boolean;
asyncError: Error;
}
interface ErrorEvent {
name: 'error';
error: Exception;
}Events that are dispatched asynchronously and pause test execution until all handlers complete.
type AsyncEvent =
| SetupEvent
| IncludeTestLocationEvent
| RunStartEvent
| RunFinishEvent
| RunDescribeStartEvent
| RunDescribeFinishEvent
| TestStartEvent
| TestDoneEvent
| TestRetryEvent
| HookStartEvent
| HookSuccessEvent
| HookFailureEvent;
interface SetupEvent {
name: 'setup';
testNamePattern?: string;
runtimeGlobals: JestGlobals;
parentProcess: NodeJS.Process;
}
interface IncludeTestLocationEvent {
name: 'include_test_location_in_result';
}
interface RunStartEvent {
name: 'run_start';
}
interface RunFinishEvent {
name: 'run_finish';
}
interface RunDescribeStartEvent {
name: 'run_describe_start';
describeBlock: DescribeBlock;
}
interface RunDescribeFinishEvent {
name: 'run_describe_finish';
describeBlock: DescribeBlock;
}
interface TestStartEvent {
name: 'test_start';
test: TestEntry;
}
interface TestDoneEvent {
name: 'test_done';
test: TestEntry;
}
interface TestRetryEvent {
name: 'test_retry';
test: TestEntry;
}
interface HookStartEvent {
name: 'hook_start';
hook: Hook;
}
interface HookSuccessEvent {
name: 'hook_success';
hook: Hook;
describeBlock?: DescribeBlock;
test?: TestEntry;
}
interface HookFailureEvent {
name: 'hook_failure';
error: Exception;
hook: Hook;
describeBlock?: DescribeBlock;
test?: TestEntry;
}Usage Examples:
import { addEventHandler } from "jest-circus";
// Handle specific event types
addEventHandler((event, state) => {
// TypeScript type narrowing
if (event.name === 'test_start') {
// event is now typed as TestStartEvent
console.log(`Test started: ${event.test.name}`);
console.log(`Parent describe: ${event.test.parent.name}`);
}
if (event.name === 'hook_failure') {
// event is now typed as HookFailureEvent
console.error(`${event.hook.type} hook failed:`, event.error);
if (event.test) {
console.error(`In test: ${event.test.name}`);
}
}
});
// Handle all test lifecycle events
addEventHandler(async (event, state) => {
const testEvents = [
'test_start',
'test_done',
'test_retry'
] as const;
if (testEvents.includes(event.name as any)) {
await logTestEvent(event, state);
}
});Important notes about event handling behavior:
start_describe_definition, finish_describe_definition, add_hook, add_test, error) do not pause test executionUsage Examples:
import { addEventHandler } from "jest-circus";
// Event handler that doesn't block
addEventHandler((event, state) => {
// Synchronous logging - won't pause tests
if (event.name === 'add_test') {
console.log(`Registered test: ${event.testName}`);
}
});
// Event handler that blocks until complete
addEventHandler(async (event, state) => {
if (event.name === 'test_done') {
// This will pause until the async operation completes
await uploadTestResults(event.test);
}
});
// Error handling in event handlers
addEventHandler(async (event, state) => {
try {
if (event.name === 'run_finish') {
await generateReport(state);
}
} catch (error) {
console.error('Report generation failed:', error);
// Don't re-throw - would stop test execution
}
});Install with Tessl CLI
npx tessl i tessl/npm-jest-circusevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10