Skip to content

Commit 1ec1348

Browse files
committed
feat: add reporters
PR-URL: nodejs/node#45712 Fixes: nodejs/node#45648 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> (cherry picked from commit a1b27b25bb01aadd3fd2714e4b136db11b7eb85a)
1 parent aeaeb73 commit 1ec1348

33 files changed

+1734
-225
lines changed

README.md

+206-34
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ Differences from the core implementation:
2424
2525
<!-- source_link=lib/test.js -->
2626

27-
The `node:test` module facilitates the creation of JavaScript tests that
28-
report results in [TAP][] format. This package is a port of `node:test`.
27+
The `node:test` module facilitates the creation of JavaScript tests.
2928
To access it:
3029

3130
```mjs
@@ -99,9 +98,7 @@ test('callback failing test', (t, done) => {
9998
})
10099
```
101100

102-
As a test file executes, TAP is written to the standard output of the Node.js
103-
process. This output can be interpreted by any test harness that understands
104-
the TAP format. If any tests fail, the process exit code is set to `1`.
101+
If any tests fail, the process exit code is set to `1`.
105102

106103
#### Subtests
107104

@@ -130,8 +127,7 @@ test to fail.
130127
## Skipping tests
131128

132129
Individual tests can be skipped by passing the `skip` option to the test, or by
133-
calling the test context's `skip()` method. Both of these options support
134-
including a message that is displayed in the TAP output as shown in the
130+
calling the test context's `skip()` method as shown in the
135131
following example.
136132

137133
```js
@@ -265,7 +261,7 @@ Test name patterns do not change the set of files that the test runner executes.
265261

266262
## Extraneous asynchronous activity
267263

268-
Once a test function finishes executing, the TAP results are output as quickly
264+
Once a test function finishes executing, the results are reported as quickly
269265
as possible while maintaining the order of the tests. However, it is possible
270266
for the test function to generate asynchronous activity that outlives the test
271267
itself. The test runner handles this type of activity, but does not delay the
@@ -274,13 +270,13 @@ reporting of test results in order to accommodate it.
274270
In the following example, a test completes with two `setImmediate()`
275271
operations still outstanding. The first `setImmediate()` attempts to create a
276272
new subtest. Because the parent test has already finished and output its
277-
results, the new subtest is immediately marked as failed, and reported in the
278-
top level of the file's TAP output.
273+
results, the new subtest is immediately marked as failed, and reported later
274+
to the {TestsStream}.
279275

280276
The second `setImmediate()` creates an `uncaughtException` event.
281277
`uncaughtException` and `unhandledRejection` events originating from a completed
282278
test are marked as failed by the `test` module and reported as diagnostic
283-
warnings in the top level of the file's TAP output.
279+
warnings at the top level by the {TestsStream}.
284280

285281
```js
286282
test('a test that creates asynchronous activity', t => {
@@ -431,6 +427,163 @@ test('spies on an object method', (t) => {
431427
});
432428
```
433429

430+
431+
## Test reporters
432+
433+
<!-- YAML
434+
added: REPLACEME
435+
-->
436+
437+
The `node:test` module supports passing [`--test-reporter`][]
438+
flags for the test runner to use a specific reporter.
439+
440+
The following built-reporters are supported:
441+
442+
* `tap`
443+
The `tap` reporter is the default reporter used by the test runner. It outputs
444+
the test results in the [TAP][] format.
445+
446+
* `spec`
447+
The `spec` reporter outputs the test results in a human-readable format.
448+
449+
* `dot`
450+
The `dot` reporter outputs the test results in a comact format,
451+
where each passing test is represented by a `.`,
452+
and each failing test is represented by a `X`.
453+
454+
### Custom reporters
455+
456+
[`--test-reporter`][] can be used to specify a path to custom reporter.
457+
a custom reporter is a module that exports a value
458+
accepted by [stream.compose][].
459+
Reporters should transform events emitted by a {TestsStream}
460+
461+
Example of a custom reporter using {stream.Transform}:
462+
463+
```mjs
464+
import { Transform } from 'node:stream';
465+
const customReporter = new Transform({
466+
writableObjectMode: true,
467+
transform(event, encoding, callback) {
468+
switch (event.type) {
469+
case 'test:start':
470+
callback(null, `test ${event.data.name} started`);
471+
break;
472+
case 'test:pass':
473+
callback(null, `test ${event.data.name} passed`);
474+
break;
475+
case 'test:fail':
476+
callback(null, `test ${event.data.name} failed`);
477+
break;
478+
case 'test:plan':
479+
callback(null, 'test plan');
480+
break;
481+
case 'test:diagnostic':
482+
callback(null, event.data.message);
483+
break;
484+
}
485+
},
486+
});
487+
export default customReporter;
488+
```
489+
490+
```cjs
491+
const { Transform } = require('node:stream');
492+
const customReporter = new Transform({
493+
writableObjectMode: true,
494+
transform(event, encoding, callback) {
495+
switch (event.type) {
496+
case 'test:start':
497+
callback(null, `test ${event.data.name} started`);
498+
break;
499+
case 'test:pass':
500+
callback(null, `test ${event.data.name} passed`);
501+
break;
502+
case 'test:fail':
503+
callback(null, `test ${event.data.name} failed`);
504+
break;
505+
case 'test:plan':
506+
callback(null, 'test plan');
507+
break;
508+
case 'test:diagnostic':
509+
callback(null, event.data.message);
510+
break;
511+
}
512+
},
513+
});
514+
module.exports = customReporter;
515+
```
516+
517+
Example of a custom reporter using a generator function:
518+
519+
```mjs
520+
export default async function * customReporter(source) {
521+
for await (const event of source) {
522+
switch (event.type) {
523+
case 'test:start':
524+
yield `test ${event.data.name} started\n`;
525+
break;
526+
case 'test:pass':
527+
yield `test ${event.data.name} passed\n`;
528+
break;
529+
case 'test:fail':
530+
yield `test ${event.data.name} failed\n`;
531+
break;
532+
case 'test:plan':
533+
yield 'test plan';
534+
break;
535+
case 'test:diagnostic':
536+
yield `${event.data.message}\n`;
537+
break;
538+
}
539+
}
540+
}
541+
```
542+
543+
```cjs
544+
module.exports = async function * customReporter(source) {
545+
for await (const event of source) {
546+
switch (event.type) {
547+
case 'test:start':
548+
yield `test ${event.data.name} started\n`;
549+
break;
550+
case 'test:pass':
551+
yield `test ${event.data.name} passed\n`;
552+
break;
553+
case 'test:fail':
554+
yield `test ${event.data.name} failed\n`;
555+
break;
556+
case 'test:plan':
557+
yield 'test plan\n';
558+
break;
559+
case 'test:diagnostic':
560+
yield `${event.data.message}\n`;
561+
break;
562+
}
563+
}
564+
};
565+
```
566+
567+
### Multiple reporters
568+
569+
The [`--test-reporter`][] flag can be specified multiple times to report test
570+
results in several formats. In this situation
571+
it is required to specify a destination for each reporter
572+
using [`--test-reporter-destination`][].
573+
Destination can be `stdout`, `stderr`, or a file path.
574+
Reporters and destinations are paired according
575+
to the order they were specified.
576+
577+
In the following example, the `spec` reporter will output to `stdout`,
578+
and the `dot` reporter will output to `file.txt`:
579+
580+
```bash
581+
node --test-reporter=spec --test-reporter=dot --test-reporter-destination=stdout --test-reporter-destination=file.txt
582+
```
583+
584+
When a single reporter is specified, the destination will default to `stdout`,
585+
unless a destination is explicitly provided.
586+
434587
## `run([options])`
435588

436589
<!-- YAML
@@ -459,7 +612,7 @@ added: REPLACEME
459612
incremented from the primary's `process.debugPort`.
460613
**Default:** `undefined`.
461614

462-
* Returns: {TapStream}
615+
* Returns: {TestsStream}
463616

464617
```js
465618
run({ files: [path.resolve('./tests/test.js')] })
@@ -501,12 +654,11 @@ run({ files: [path.resolve('./tests/test.js')] })
501654
- Returns: {Promise} Resolved with `undefined` once the test completes.
502655

503656
The `test()` function is the value imported from the `test` module. Each
504-
invocation of this function results in the creation of a test point in the TAP
505-
output.
657+
invocation of this function results in reporting the test to the {TestsStream}.
506658

507659
The `TestContext` object passed to the `fn` argument can be used to perform
508660
actions related to the current test. Examples include skipping the test, adding
509-
additional TAP diagnostic information, or creating subtests.
661+
additional diagnostic information, or creating subtests.
510662

511663
`test()` returns a `Promise` that resolves once the test completes. The return
512664
value can usually be discarded for top level tests. However, the return value
@@ -545,8 +697,7 @@ thus prevent the scheduled cancellation.
545697
* Returns: `undefined`.
546698

547699
The `describe()` function imported from the `test` module. Each
548-
invocation of this function results in the creation of a Subtest
549-
and a test point in the TAP output.
700+
invocation of this function results in the creation of a Subtest.
550701
After invocation of top level `describe` functions,
551702
all top level tests and suites will execute.
552703

@@ -572,8 +723,6 @@ Shorthand for marking a suite as `TODO`, same as
572723
* Returns: `undefined`.
573724

574725
The `it()` function is the value imported from the `test` module.
575-
Each invocation of this function results in the creation of a test point in the
576-
TAP output.
577726

578727
## `it.skip([name][, options][, fn])`
579728

@@ -961,46 +1110,69 @@ added: REPLACEME
9611110
This function is syntax sugar for [`MockTracker.method`][] with `options.setter`
9621111
set to `true`.
9631112

964-
## Class: `TapStream`
1113+
## Class: `TestsStream`
9651114

9661115
<!-- YAML
9671116
added: REPLACEME
9681117
-->
9691118

9701119
* Extends {ReadableStream}
9711120

972-
A successful call to [`run()`][] method will return a new {TapStream}
973-
object, streaming a [TAP][] output
974-
`TapStream` will emit events, in the order of the tests definition
1121+
A successful call to [`run()`][] method will return a new {TestsStream}
1122+
object, streaming a series of events representing the execution of the tests.
1123+
`TestsStream` will emit events, in the order of the tests definition
9751124

9761125
### Event: `'test:diagnostic'`
9771126

978-
* `message` {string} The diagnostic message.
1127+
* `data` {Object}
1128+
* `message` {string} The diagnostic message.
1129+
* `nesting` {number} The nesting level of the test.
9791130

9801131
Emitted when [`context.diagnostic`][] is called.
9811132

9821133
### Event: `'test:fail'`
9831134

9841135
* `data` {Object}
9851136
* `details` {Object} Additional execution metadata.
1137+
* `duration` {number} The duration of the test in milliseconds.
1138+
* `error` {Error} The error thrown by the test.
9861139
* `name` {string} The test name.
1140+
* `nesting` {number} The nesting level of the test.
9871141
* `testNumber` {number} The ordinal number of the test.
988-
* `todo` {string|undefined} Present if [`context.todo`][] is called
989-
* `skip` {string|undefined} Present if [`context.skip`][] is called
1142+
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
1143+
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
9901144

9911145
Emitted when a test fails.
9921146

9931147
### Event: `'test:pass'`
9941148

9951149
* `data` {Object}
9961150
* `details` {Object} Additional execution metadata.
1151+
* `duration` {number} The duration of the test in milliseconds.
9971152
* `name` {string} The test name.
1153+
* `nesting` {number} The nesting level of the test.
9981154
* `testNumber` {number} The ordinal number of the test.
999-
* `todo` {string|undefined} Present if [`context.todo`][] is called
1000-
* `skip` {string|undefined} Present if [`context.skip`][] is called
1155+
* `todo` {string|boolean|undefined} Present if [`context.todo`][] is called
1156+
* `skip` {string|boolean|undefined} Present if [`context.skip`][] is called
10011157

10021158
Emitted when a test passes.
10031159

1160+
### Event: `'test:plan'`
1161+
1162+
* `data` {Object}
1163+
* `nesting` {number} The nesting level of the test.
1164+
* `count` {number} The number of subtests that have ran.
1165+
1166+
Emitted when all subtests have completed for a given test.
1167+
1168+
### Event: `'test:start'`
1169+
1170+
* `data` {Object}
1171+
* `name` {string} The test name.
1172+
* `nesting` {number} The nesting level of the test.
1173+
1174+
Emitted when a test starts.
1175+
10041176
## Class: `TestContext`
10051177

10061178
An instance of `TestContext` is passed to each test function in order to
@@ -1092,9 +1264,9 @@ test('top level test', async (t) => {
10921264

10931265
### `context.diagnostic(message)`
10941266

1095-
- `message` {string} Message to be displayed as a TAP diagnostic.
1267+
- `message` {string}Message to be reported.
10961268

1097-
This function is used to write TAP diagnostics to the output. Any diagnostic
1269+
This function is used to write diagnostics to the output. Any diagnostic
10981270
information is included at the end of the test's results. This function does
10991271
not return a value.
11001272

@@ -1128,19 +1300,19 @@ test('top level test', async (t) => {
11281300

11291301
### `context.skip([message])`
11301302

1131-
- `message` {string} Optional skip message to be displayed in TAP output.
1303+
* `message` {string} Optional skip message.
11321304

11331305
This function causes the test's output to indicate the test as skipped. If
1134-
`message` is provided, it is included in the TAP output. Calling `skip()` does
1306+
`message` is provided, it is included in the output. Calling `skip()` does
11351307
not terminate execution of the test function. This function does not return a
11361308
value.
11371309

11381310
### `context.todo([message])`
11391311

1140-
- `message` {string} Optional `TODO` message to be displayed in TAP output.
1312+
* `message` {string} Optional `TODO` message.
11411313

11421314
This function adds a `TODO` directive to the test's output. If `message` is
1143-
provided, it is included in the TAP output. Calling `todo()` does not terminate
1315+
provided, it is included in the output. Calling `todo()` does not terminate
11441316
execution of the test function. This function does not return a value.
11451317

11461318
### `context.test([name][, options][, fn])`

0 commit comments

Comments
 (0)