0
# Factory System
1
2
Test runner factory for testing reporters, plugins, and creating isolated test environments.
3
4
## Capabilities
5
6
### Runner Factory Function
7
8
Creates a new instance of the runner factory for testing and development purposes.
9
10
```typescript { .api }
11
/**
12
* Create an instance of the runner factory
13
* @returns RunnerFactory instance for creating isolated test environments
14
*/
15
function runner(): RunnerFactory;
16
```
17
18
### RunnerFactory Class
19
20
Factory class that provides an API to run dummy suites, groups, and tests for testing reporters and plugins.
21
22
```typescript { .api }
23
/**
24
* Runner factory exposes the API to run dummy suites, groups and tests.
25
* You might want to use the factory for testing reporters and plugins usage.
26
*/
27
class RunnerFactory {
28
/** Configure the factory with test configuration and CLI arguments */
29
configure(config: Config, argv?: string[]): this;
30
31
/** Define a custom emitter instance to use */
32
useEmitter(emitter: Emitter): this;
33
34
/** Run a single test using the runner */
35
async runTest(title: string, callback: TestExecutor<TestContext, undefined>): Promise<RunnerSummary>;
36
37
/** Enable/disable the bail mode */
38
bail(toggle?: boolean): this;
39
40
/** Run dummy test suites */
41
async runSuites(suites: (emitter: Emitter, refiner: Refiner, file?: string) => Suite[]): Promise<RunnerSummary>;
42
}
43
```
44
45
**Usage Examples:**
46
47
```typescript
48
import { runner } from "@japa/runner/factories";
49
import { assert } from "chai";
50
51
// Create factory instance
52
const factory = runner();
53
54
// Configure the factory
55
factory.configure({
56
files: ["tests/**/*.spec.ts"],
57
reporters: {
58
activated: ["spec"],
59
},
60
});
61
62
// Run a single test
63
const summary = await factory.runTest("should work", async (ctx) => {
64
assert.equal(2 + 2, 4);
65
});
66
67
console.log(`Test result: ${summary.hasError ? "FAILED" : "PASSED"}`);
68
```
69
70
### Testing Reporters
71
72
Use the factory to test custom reporters in isolation.
73
74
**Usage Examples:**
75
76
```typescript
77
import { runner } from "@japa/runner/factories";
78
import { Emitter, Suite, Test, TestContext } from "@japa/runner/core";
79
import { assert } from "chai";
80
81
// Custom reporter to test
82
const customReporter = {
83
name: "test-reporter",
84
handler: (runner, emitter) => {
85
const results: string[] = [];
86
87
emitter.on("test:start", (payload) => {
88
results.push(`START: ${payload.title}`);
89
});
90
91
emitter.on("test:end", (payload) => {
92
const status = payload.hasError ? "FAIL" : "PASS";
93
results.push(`END: ${payload.title} - ${status}`);
94
});
95
96
return { results };
97
},
98
};
99
100
// Test the reporter
101
const factory = runner();
102
factory.configure({
103
files: [],
104
reporters: {
105
activated: ["test-reporter"],
106
list: [customReporter],
107
},
108
});
109
110
// Run test with custom reporter
111
const summary = await factory.runTest("sample test", async (ctx) => {
112
assert.isTrue(true);
113
});
114
115
// Verify reporter output
116
console.log("Reporter captured:", customReporter.handler().results);
117
```
118
119
### Testing Plugins
120
121
Use the factory to test custom plugins in isolation.
122
123
**Usage Examples:**
124
125
```typescript
126
import { runner } from "@japa/runner/factories";
127
import { assert } from "chai";
128
129
// Custom plugin to test
130
const testPlugin = () => {
131
const events: string[] = [];
132
133
return ({ emitter }) => {
134
emitter.on("runner:start", () => {
135
events.push("runner started");
136
});
137
138
emitter.on("test:start", (payload) => {
139
events.push(`test started: ${payload.title}`);
140
});
141
142
emitter.on("test:end", (payload) => {
143
events.push(`test ended: ${payload.title}`);
144
});
145
146
emitter.on("runner:end", () => {
147
events.push("runner ended");
148
});
149
150
// Expose events for testing
151
return { events };
152
};
153
};
154
155
// Test the plugin
156
const factory = runner();
157
const pluginInstance = testPlugin();
158
159
factory.configure({
160
files: [],
161
plugins: [pluginInstance],
162
});
163
164
// Run test with plugin
165
await factory.runTest("plugin test", async (ctx) => {
166
assert.equal(1 + 1, 2);
167
});
168
169
// Verify plugin behavior
170
console.log("Plugin events:", pluginInstance().events);
171
```
172
173
### Running Multiple Test Suites
174
175
Create and run complex test scenarios with multiple suites.
176
177
**Usage Examples:**
178
179
```typescript
180
import { runner } from "@japa/runner/factories";
181
import { Suite, Test, TestContext, Group, Emitter, Refiner } from "@japa/runner/core";
182
import { assert } from "chai";
183
184
const factory = runner();
185
186
factory.configure({
187
files: [],
188
reporters: {
189
activated: ["spec"],
190
},
191
});
192
193
// Create test suites programmatically
194
const summary = await factory.runSuites((emitter, refiner, file) => {
195
// Unit tests suite
196
const unitSuite = new Suite("unit", emitter, refiner);
197
198
const mathGroup = new Group("Math operations", emitter, refiner);
199
const addTest = new Test("should add numbers", (test) => new TestContext(test), emitter, refiner, mathGroup);
200
addTest.run(async (ctx) => {
201
assert.equal(2 + 3, 5);
202
}, new Error());
203
204
unitSuite.add(mathGroup);
205
206
// Integration tests suite
207
const integrationSuite = new Suite("integration", emitter, refiner);
208
209
const apiTest = new Test("should call API", (test) => new TestContext(test), emitter, refiner);
210
apiTest.run(async (ctx) => {
211
// Mock API call
212
const result = await Promise.resolve({ status: "ok" });
213
assert.equal(result.status, "ok");
214
}, new Error());
215
216
integrationSuite.add(apiTest);
217
218
return [unitSuite, integrationSuite];
219
});
220
221
console.log(`Ran ${summary.aggregates.total} tests, ${summary.aggregates.passed} passed`);
222
```
223
224
### Factory with Custom Emitter
225
226
Use a custom emitter to capture and analyze test events.
227
228
**Usage Examples:**
229
230
```typescript
231
import { runner } from "@japa/runner/factories";
232
import { Emitter } from "@japa/runner/core";
233
import { assert } from "chai";
234
235
// Custom emitter with event logging
236
class LoggingEmitter extends Emitter {
237
private eventLog: Array<{ event: string; timestamp: number; payload?: any }> = [];
238
239
emit(event: string, ...args: any[]) {
240
this.eventLog.push({
241
event,
242
timestamp: Date.now(),
243
payload: args.length === 1 ? args[0] : args,
244
});
245
246
return super.emit(event, ...args);
247
}
248
249
getEventLog() {
250
return this.eventLog;
251
}
252
253
clearLog() {
254
this.eventLog = [];
255
}
256
}
257
258
// Use custom emitter with factory
259
const customEmitter = new LoggingEmitter();
260
const factory = runner();
261
262
factory
263
.useEmitter(customEmitter)
264
.configure({
265
files: [],
266
});
267
268
// Run test and capture events
269
await factory.runTest("event logging test", async (ctx) => {
270
assert.isTrue(true);
271
});
272
273
// Analyze captured events
274
const events = customEmitter.getEventLog();
275
console.log("Captured events:", events.map(e => e.event));
276
```
277
278
### Bail Mode Testing
279
280
Test bail mode behavior where execution stops on first failure.
281
282
**Usage Examples:**
283
284
```typescript
285
import { runner } from "@japa/runner/factories";
286
import { assert } from "chai";
287
288
const factory = runner();
289
290
// Enable bail mode
291
factory
292
.bail(true)
293
.configure({
294
files: [],
295
});
296
297
// Run suites with bail mode
298
const summary = await factory.runSuites((emitter, refiner) => {
299
const suite = new Suite("bail test", emitter, refiner);
300
301
// First test (will fail)
302
const failingTest = new Test("failing test", (test) => new TestContext(test), emitter, refiner);
303
failingTest.run(async (ctx) => {
304
assert.equal(1, 2); // This will fail
305
}, new Error());
306
307
// Second test (should not run due to bail)
308
const passingTest = new Test("passing test", (test) => new TestContext(test), emitter, refiner);
309
passingTest.run(async (ctx) => {
310
assert.equal(1, 1);
311
}, new Error());
312
313
suite.add(failingTest);
314
suite.add(passingTest);
315
316
return [suite];
317
});
318
319
console.log(`Bail mode: ${summary.aggregates.total} total, ${summary.aggregates.failed} failed`);
320
```
321
322
### Sync Reporter
323
324
Built-in synchronous reporter for testing that throws on test failures.
325
326
```typescript { .api }
327
/**
328
* Synchronous reporter that throws errors on test failures
329
* Useful for testing scenarios where you want immediate failure feedback
330
*/
331
const syncReporter: ReporterContract;
332
```
333
334
**Usage Examples:**
335
336
```typescript
337
import { runner, syncReporter } from "@japa/runner/factories";
338
import { assert } from "chai";
339
340
const factory = runner();
341
342
factory.configure({
343
files: [],
344
reporters: {
345
activated: ["sync"],
346
list: [syncReporter],
347
},
348
});
349
350
try {
351
// This will throw if any test fails
352
await factory.runTest("failing test", async (ctx) => {
353
assert.equal(1, 2);
354
});
355
} catch (error) {
356
console.log("Test failed as expected:", error.message);
357
}
358
```
359
360
### Create Dummy Tests
361
362
Utility function for creating test data and scenarios.
363
364
```typescript { .api }
365
/**
366
* Create dummy tests for testing and development purposes
367
*/
368
function createDummyTests(): any; // Implementation specific
369
```
370
371
## Types
372
373
### Factory Types
374
375
```typescript { .api }
376
interface RunnerFactory {
377
configure(config: Config, argv?: string[]): this;
378
useEmitter(emitter: Emitter): this;
379
runTest(title: string, callback: TestExecutor<TestContext, undefined>): Promise<RunnerSummary>;
380
bail(toggle?: boolean): this;
381
runSuites(suites: (emitter: Emitter, refiner: Refiner, file?: string) => Suite[]): Promise<RunnerSummary>;
382
}
383
384
interface RunnerSummary {
385
hasError: boolean;
386
aggregates: {
387
passed: number;
388
failed: number;
389
skipped: number;
390
todo: number;
391
total: number;
392
};
393
failureTree: Array<{
394
name: string;
395
errors: Array<{ phase: string; error: Error }>;
396
children: Array<{
397
name: string;
398
errors: Array<{ phase: string; error: Error }>;
399
}>;
400
}>;
401
}
402
```
403
404
### Suite Factory Function Type
405
406
```typescript { .api }
407
type SuiteFactory = (emitter: Emitter, refiner: Refiner, file?: string) => Suite[];
408
```