diff --git a/doc/api/test.md b/doc/api/test.md index 749b6ff08a5396..8026df5b2f3932 100644 --- a/doc/api/test.md +++ b/doc/api/test.md @@ -509,7 +509,13 @@ test('spies on an object method', (t) => { ## Test reporters The `node:test` module supports passing [`--test-reporter`][] @@ -531,6 +537,21 @@ The following built-reporters are supported: When `stdout` is a [TTY][], the `spec` reporter is used by default. Otherwise, the `tap` reporter is used by default. +The exact output of these reporters is subject to change between versions of +Node.js, and should not be relied on programmatically. If programmatic access +to the test runner's output is required, use the events emitted by the +{TestsStream}. + +The reporters are available via the `node:test/reporters` module: + +```mjs +import { tap, spec, dot } from 'node:test/reporters'; +``` + +```cjs +const { tap, spec, dot } = require('node:test/reporters'); +``` + ### Custom reporters [`--test-reporter`][] can be used to specify a path to custom reporter. @@ -722,8 +743,20 @@ added: v18.9.0 **Default:** `undefined`. * Returns: {TestsStream} -```js +```mjs +import { tap } from 'node:test/reporters'; +import process from 'node:process'; + +run({ files: [path.resolve('./tests/test.js')] }) + .compose(tap) + .pipe(process.stdout); +``` + +```cjs +const { tap } = require('node:test/reporters'); + run({ files: [path.resolve('./tests/test.js')] }) + .compose(tap) .pipe(process.stdout); ``` diff --git a/lib/test/reporters.js b/lib/test/reporters.js new file mode 100644 index 00000000000000..287c07510bc13a --- /dev/null +++ b/lib/test/reporters.js @@ -0,0 +1,38 @@ +'use strict'; + +const { ObjectDefineProperties } = primordials; + +let dot; +let spec; +let tap; + +ObjectDefineProperties(module.exports, { + __proto__: null, + dot: { + __proto__: null, + configurable: true, + enumerable: true, + get() { + dot ??= require('internal/test_runner/reporter/dot'); + return dot; + }, + }, + spec: { + __proto__: null, + configurable: true, + enumerable: true, + get() { + spec ??= require('internal/test_runner/reporter/spec'); + return spec; + }, + }, + tap: { + __proto__: null, + configurable: true, + enumerable: true, + get() { + tap ??= require('internal/test_runner/reporter/tap'); + return tap; + }, + }, +}); diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index 6ac007bfb5dd6c..b37bdf94f38c02 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -2,6 +2,7 @@ import * as common from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import { join } from 'node:path'; import { describe, it, run } from 'node:test'; +import { dot, spec, tap } from 'node:test/reporters'; import assert from 'node:assert'; const testFixtures = fixtures.path('test-runner'); @@ -65,4 +66,39 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { code: 'ERR_INVALID_ARG_TYPE' })); }); + + it('should be piped with dot', async () => { + const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(dot).toArray(); + assert.deepStrictEqual(result, [ + '.', + '\n', + ]); + }); + + it('should be piped with spec', async () => { + const specReporter = new spec(); + const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(specReporter).toArray(); + const stringResults = result.map((bfr) => bfr.toString()); + assert.match(stringResults[0], /this should pass/); + assert.match(stringResults[1], /tests 1/); + assert.match(stringResults[1], /pass 1/); + }); + + it('should be piped with tap', async () => { + const result = await run({ files: [join(testFixtures, 'test/random.cjs')] }).compose(tap).toArray(); + assert.strictEqual(result.length, 13); + assert.strictEqual(result[0], 'TAP version 13\n'); + assert.strictEqual(result[1], '# Subtest: this should pass\n'); + assert.strictEqual(result[2], 'ok 1 - this should pass\n'); + assert.match(result[3], /duration_ms: \d+\.?\d*/); + assert.strictEqual(result[4], '1..1\n'); + assert.strictEqual(result[5], '# tests 1\n'); + assert.strictEqual(result[6], '# suites 0\n'); + assert.strictEqual(result[7], '# pass 1\n'); + assert.strictEqual(result[8], '# fail 0\n'); + assert.strictEqual(result[9], '# cancelled 0\n'); + assert.strictEqual(result[10], '# skipped 0\n'); + assert.strictEqual(result[11], '# todo 0\n'); + assert.match(result[12], /# duration_ms \d+\.?\d*/); + }); });