How to customize the format of the test output in Node.js native testing module with existing Node tap formatters?

548 Views Asked by At

I read here an intriguing snippet:

Back to the Node.js test runner, it outputs in TAP, which is the “test anything protocol”, here’s the specification: testanything.org/tap-specification.html.

That means you can take your output and pipe it into existing formatters and there was already node-tap as a userland runner implementation.

The default native Node.js test output is terrible, how can I wire up a basic hello-world test using a community TAP formatter?

This tap-parser seems like a good candidate, but what I'm missing is how to connect that around the Node.js node:test module. Can you show a quick hello world script on how to write a test and customize the output formatting using something like this parser, or any other mechanism?

I have this in a test file:

import { run } from 'node:test'

const files = ['./example.test.js']

// --test-name-pattern="test [1-3]"
run({ files }).pipe(process.stdout)

And I have in example.test.js:

import test from 'node:test'

test('foo', () => {
  console.log('start')
})

However, I am getting empty output:

$ node ./test.js
TAP version 13
# Subtest: ./example.test.js
ok 1 - ./example.test.js
  ---
  duration_ms: 415.054557
  ...
1..1
# tests 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 417.190938

Any ideas what I'm missing? When I run the file directly, it works:

$ node ./example.test.js
TAP version 13
# Subtest: foo
ok 1 - foo
  ---
  duration_ms: 1.786378
  ...
1
# tests 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 210.586371

It looks like if I throw an error in the nested example.test.js test, the error will surface in the test.js file. However, how do I get access to the underlying test data?

2

There are 2 best solutions below

0
Lance On

Seems the best way so far is to just go low-level like this:

import cp from 'child_process'
import { Parser } from 'tap-parser'

const p = new Parser(results => console.dir(results))

p.on('pass', data => console.log('pass', data))
p.on('fail', data => console.log('fail', data))

const child = cp.spawn(
  'node',
  ['./host/link/test/parser/index.test.js'],
  {
    stdio: [null, 'pipe', 'inherit'],
  },
)

child.stdout.pipe(p)
0
Chen Peleg On

If anyone else is interested, I found another (a bit hacky) solution, to get all the Tap data as string (and then do whatever withit):

// getTapData.js 
import {run} from 'node:test';

const getTapDataAsync = (testFiles) => {
   let allData = '';
   return new Promise((resolve, reject) => {
      const stream = run({
        files: testFiles,
      });
      stream.on('data', (data) => (allData += data.toString()));
      stream.on('close', (data) => resolve(allData));
      stream.on('error', (err) => reject(err));
   });
};

Then, you can call that function and get all of the Tap/Node unit test data as string:

// testRunner.js

 const tapAsString = await getTapDataAsync(testFiles);
 console.log(tapAsString);