0
# Core Classes
1
2
Low-level classes for advanced usage, testing frameworks, and plugin development.
3
4
## Capabilities
5
6
### Runner Class
7
8
Test execution engine that manages suites, reporters, and the overall test lifecycle.
9
10
```typescript { .api }
11
/**
12
* Runner class is used to execute the tests
13
*/
14
class Runner {
15
/** Enable or disable bail mode (stop on first failure) */
16
bail(toggle?: boolean): this;
17
18
/** Register a reporter for test output */
19
registerReporter(reporter: ReporterContract): void;
20
21
/** Get test execution summary */
22
getSummary(): RunnerSummary;
23
24
/** Register callback for suite events */
25
onSuite(callback: (suite: Suite) => void): void;
26
27
/** Add a suite to the runner */
28
add(suite: Suite): void;
29
30
/** Start test execution */
31
async start(): Promise<void>;
32
33
/** Execute all tests */
34
async exec(): Promise<void>;
35
36
/** Finish test execution */
37
async end(): Promise<void>;
38
}
39
40
interface RunnerSummary {
41
hasError: boolean;
42
aggregates: {
43
passed: number;
44
failed: number;
45
skipped: number;
46
todo: number;
47
total: number;
48
};
49
failureTree: Array<{
50
name: string;
51
errors: Array<{ phase: string; error: Error }>;
52
children: Array<{
53
name: string;
54
errors: Array<{ phase: string; error: Error }>;
55
}>;
56
}>;
57
}
58
```
59
60
**Usage Examples:**
61
62
```typescript
63
import { Runner, Emitter, Suite } from "@japa/runner/core";
64
65
// Create and configure runner
66
const emitter = new Emitter();
67
const runner = new Runner(emitter);
68
69
// Enable bail mode
70
runner.bail(true);
71
72
// Register custom reporter
73
runner.registerReporter({
74
name: "custom",
75
handler: (runner, emitter) => {
76
emitter.on("test:end", (payload) => {
77
console.log(`Test: ${payload.title} - ${payload.hasError ? "FAIL" : "PASS"}`);
78
});
79
},
80
});
81
82
// Add test suites
83
const unitSuite = new Suite("unit", emitter, refiner);
84
runner.add(unitSuite);
85
86
// Execute tests
87
await runner.start();
88
await runner.exec();
89
await runner.end();
90
91
// Get results
92
const summary = runner.getSummary();
93
console.log(`Tests: ${summary.aggregates.total}, Passed: ${summary.aggregates.passed}`);
94
```
95
96
### Test Class
97
98
Individual test instance with enhanced functionality and assertion capabilities.
99
100
```typescript { .api }
101
/**
102
* Test class represents an individual test and exposes API to tweak its runtime behavior
103
*/
104
class Test<TestData = undefined> {
105
/** Assert the test throws an exception with a certain error message */
106
throws(message: string | RegExp, errorConstructor?: any): this;
107
108
/** Set timeout for this test */
109
timeout(duration: number): this;
110
111
/** Set retry count for this test */
112
retry(count: number): this;
113
114
/** Mark test as todo (not implemented) */
115
todo(): this;
116
117
/** Mark test as skipped */
118
skip(): this;
119
120
/** Pin test (only run this test) */
121
pin(): this;
122
123
/** Add test setup hook */
124
setup(handler: TestHooksHandler<TestContext>): this;
125
126
/** Add test cleanup hook */
127
cleanup(handler: TestHooksCleanupHandler<TestContext>): this;
128
129
/** Execute the test */
130
run(executor: TestExecutor<TestContext, TestData>, debuggingError: Error): this;
131
}
132
133
type TestHooksHandler<Context> = (context: Context) => void | Promise<void>;
134
type TestHooksCleanupHandler<Context> = (context: Context) => void | Promise<void>;
135
```
136
137
**Usage Examples:**
138
139
```typescript
140
import { Test, TestContext, Emitter, Refiner } from "@japa/runner/core";
141
142
// Create test instance
143
const emitter = new Emitter();
144
const refiner = new Refiner();
145
const test = new Test("should validate input", (test) => new TestContext(test), emitter, refiner);
146
147
// Configure test
148
test
149
.timeout(5000)
150
.retry(2)
151
.setup(async (context) => {
152
context.database = await setupTestDatabase();
153
})
154
.cleanup(async (context) => {
155
await cleanupTestDatabase(context.database);
156
});
157
158
// Test that expects an exception
159
const errorTest = new Test("should throw error", (test) => new TestContext(test), emitter, refiner);
160
errorTest
161
.throws("Invalid input", ValidationError)
162
.run(async (ctx) => {
163
throw new ValidationError("Invalid input");
164
}, new Error());
165
166
// Pin test for focused debugging
167
const debugTest = new Test("debug test", (test) => new TestContext(test), emitter, refiner);
168
debugTest
169
.pin()
170
.run(async (ctx) => {
171
// This test will run exclusively when pinned
172
// Note: You would need to import assert from your assertion library
173
// assert.isTrue(true);
174
}, new Error());
175
```
176
177
### TestContext Class
178
179
Test context that carries data and provides utilities for individual tests.
180
181
```typescript { .api }
182
/**
183
* Test context carries context data for a given test
184
*/
185
class TestContext {
186
/** The test instance this context belongs to */
187
test: Test;
188
189
/** Register a cleanup function that runs after the test finishes */
190
cleanup(cleanupCallback: TestHooksCleanupHandler<TestContext>): void;
191
192
constructor(test: Test);
193
}
194
```
195
196
**Usage Examples:**
197
198
```typescript
199
import { test } from "@japa/runner";
200
201
test("should handle cleanup", async (ctx) => {
202
// Set up resources
203
const connection = await createDatabaseConnection();
204
const tempFile = await createTempFile();
205
206
// Register cleanup functions
207
ctx.cleanup(async () => {
208
await connection.close();
209
console.log("Database connection closed");
210
});
211
212
ctx.cleanup(async () => {
213
await deleteTempFile(tempFile);
214
console.log("Temp file deleted");
215
});
216
217
// Test logic
218
await connection.query("SELECT 1");
219
await writeToFile(tempFile, "test data");
220
221
// Cleanup functions will run automatically after test completion
222
});
223
```
224
225
### Group Class
226
227
Container for organizing related tests with shared configuration and hooks.
228
229
```typescript { .api }
230
/**
231
* TestGroup is used to bulk configure a collection of tests and define lifecycle hooks
232
*/
233
class Group {
234
/** Add a test to this group */
235
add(test: Test): void;
236
237
/** Enable bail mode for this group */
238
bail(toggle?: boolean): this;
239
240
/** Set timeout for all tests in group */
241
timeout(duration: number): this;
242
243
/** Set retry count for all tests in group */
244
retry(count: number): this;
245
246
/** Add group setup hook (runs once before all tests) */
247
setup(handler: GroupHooksHandler<TestContext>): this;
248
249
/** Add group teardown hook (runs once after all tests) */
250
teardown(handler: GroupHooksHandler<TestContext>): this;
251
252
/** Add hooks that run before/after each test */
253
each: {
254
setup(handler: GroupHooksHandler<TestContext>): Group;
255
teardown(handler: GroupHooksHandler<TestContext>): Group;
256
};
257
258
/** Apply function to all tests in group */
259
tap(handler: (test: Test) => void): this;
260
}
261
262
type GroupHooksHandler<Context> = (context: Context) => void | Promise<void>;
263
```
264
265
**Usage Examples:**
266
267
```typescript
268
import { Group, Suite, Emitter, Refiner } from "@japa/runner/core";
269
270
// Create group
271
const emitter = new Emitter();
272
const refiner = new Refiner();
273
const group = new Group("Database Tests", emitter, refiner);
274
275
// Configure group
276
group
277
.timeout(10000)
278
.retry(1)
279
.bail(false);
280
281
// Add group-level hooks
282
group.setup(async (context) => {
283
console.log("Setting up database for all tests");
284
context.db = await setupDatabase();
285
});
286
287
group.teardown(async (context) => {
288
console.log("Cleaning up database after all tests");
289
await cleanupDatabase(context.db);
290
});
291
292
// Add per-test hooks
293
group.each.setup(async (context) => {
294
await context.db.beginTransaction();
295
});
296
297
group.each.teardown(async (context) => {
298
await context.db.rollbackTransaction();
299
});
300
301
// Apply configuration to all tests
302
group.tap((test) => {
303
test.timeout(5000);
304
});
305
```
306
307
### Suite Class
308
309
Top-level container representing a collection of tests and groups for a specific testing category.
310
311
```typescript { .api }
312
/**
313
* A suite is a collection of tests created around a given testing type
314
*/
315
class Suite {
316
/** The suite name */
317
name: string;
318
319
/** Add a test or group to this suite */
320
add(testOrGroup: Test | Group): void;
321
322
/** Enable bail mode for this suite */
323
bail(toggle?: boolean): this;
324
325
/** Set timeout for all tests in suite */
326
timeout(duration: number): this;
327
328
/** Set retry count for all tests in suite */
329
retry(count: number): this;
330
331
/** Register callback for group events */
332
onGroup(callback: (group: Group) => void): void;
333
334
/** Register callback for test events */
335
onTest(callback: (test: Test) => void): void;
336
}
337
```
338
339
**Usage Examples:**
340
341
```typescript
342
import { Suite, Group, Test, Emitter, Refiner } from "@japa/runner/core";
343
344
// Create suite
345
const emitter = new Emitter();
346
const refiner = new Refiner();
347
const suite = new Suite("Integration Tests", emitter, refiner);
348
349
// Configure suite
350
suite
351
.timeout(30000)
352
.bail(true);
353
354
// Listen to events
355
suite.onGroup((group) => {
356
console.log(`Group added to suite: ${group.title}`);
357
});
358
359
suite.onTest((test) => {
360
console.log(`Test added to suite: ${test.title}`);
361
});
362
363
// Add tests and groups
364
const apiGroup = new Group("API Tests", emitter, refiner);
365
suite.add(apiGroup);
366
367
const standaloneTest = new Test("standalone test", (test) => new TestContext(test), emitter, refiner);
368
suite.add(standaloneTest);
369
```
370
371
### Emitter Class
372
373
Event emitter for communication between test runner components.
374
375
```typescript { .api }
376
/**
377
* Event emitter for test runner communication
378
*/
379
class Emitter {
380
/** Listen to an event */
381
on(event: string, callback: (...args: any[]) => void): void;
382
383
/** Emit an event */
384
emit(event: string, ...args: any[]): void;
385
386
/** Listen to an event once */
387
once(event: string, callback: (...args: any[]) => void): void;
388
389
/** Remove event listener */
390
off(event: string, callback: (...args: any[]) => void): void;
391
}
392
```
393
394
**Usage Examples:**
395
396
```typescript
397
import { Emitter } from "@japa/runner/core";
398
399
const emitter = new Emitter();
400
401
// Listen to events
402
emitter.on("test:start", (payload) => {
403
console.log(`Test started: ${payload.title}`);
404
});
405
406
emitter.on("test:end", (payload) => {
407
const status = payload.hasError ? "FAILED" : "PASSED";
408
console.log(`Test ${status}: ${payload.title}`);
409
});
410
411
// Emit events
412
emitter.emit("test:start", { title: "My Test" });
413
emitter.emit("test:end", { title: "My Test", hasError: false });
414
415
// One-time listener
416
emitter.once("runner:end", () => {
417
console.log("All tests completed");
418
});
419
```
420
421
## Types
422
423
### Core Class Types
424
425
```typescript { .api }
426
interface TestExecutor<Context, TestData> {
427
(context: Context, done?: Function): void | Promise<void>;
428
}
429
430
interface ReporterContract {
431
name: string;
432
handler: (runner: Runner, emitter: Emitter) => void;
433
}
434
435
interface TestHooksHandler<Context> {
436
(context: Context): void | Promise<void>;
437
}
438
439
interface TestHooksCleanupHandler<Context> {
440
(context: Context): void | Promise<void>;
441
}
442
443
interface GroupHooksHandler<Context> {
444
(context: Context): void | Promise<void>;
445
}
446
```
447
448
### Summary and Result Types
449
450
```typescript { .api }
451
interface RunnerSummary {
452
hasError: boolean;
453
aggregates: {
454
passed: number;
455
failed: number;
456
skipped: number;
457
todo: number;
458
total: number;
459
};
460
failureTree: Array<{
461
name: string;
462
errors: Array<{ phase: string; error: Error }>;
463
children: Array<{
464
name: string;
465
errors: Array<{ phase: string; error: Error }>;
466
}>;
467
}>;
468
}
469
```