/ #node:test #node #testing 

Node Test Runner: skip a test if an environment variable is missing/empty

The node:test module is a built-in test runner and orchestrator in Node.js. It’s available as experimental since Node 18 but has stabilised in Node 20 and some features are still experimental at the time of writing, in Node 21.

node:test supports test skipping throught multiple approaches but the two we’ll focus on here are the “programmatic skips”, where at runtime we can decide whether a test should be skipped or not.

This can be useful for integration or end to end testing scenarios being driven via the Node.js test runner (using node:test).

Table of Contents

You can skip to the examples in the node-test-runner-examples repository:

Using { skip: VARIABLE } options/config to programmatically skip a test in node:test

The test, it and describe exports from node:test take an optional “options” parameter as its second parameter, ie. test('test-name', options, () => {}).

You can read more about test() options parameter in the Node.js docs.

The full example for this section is available on GitHub: node-test-runner-examples/src/01.03-programmatic-skip.test.js.

Assuming we need REQUIRED_ENV_VARIABLE to be set we can pass { skip: !process.env.REQUIRED_ENV_VARIABLE } in order to skip test execution when the value is not populated.

import { test } from 'node:test';
import assert from 'node:assert/strict';

test(
  'skips using config if environment variable is missing',
  { skip: !process.env.REQUIRED_ENV_VARIABLE },
  () => {
    assert.fail('if we get here, we need REQUIRED_ENV_VARIABLE and fail');
  }
);

When we run this test, we get the following output:

node src/01.03-programmatic-skip.test.js
﹣ skips using config if environment variable is missing (0.558584ms) # SKIP
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 4.818875

Passing { skip: VAR } as the options to node:test’s test function allows us to express simple “skip conditions”.

We’ve now seen how to use the Node.js test() options parameter to skip a test programmatically based on the value of an environment variable. Next we’ll see how to use t.skip() to achieve the same outcome from the body of the test.

Using t.skip to programmatically skip a test in node:test

The full example for this section is available on GitHub: node-test-runner-examples/src/01.03-programmatic-skip.test.js.

The test function of node:test passes a “test context” to the “test body”. This test context is usually called t in the test implementation function and it contains a .skip() function.

We can therefore write the following test, where we don’t use options.skip = true as in the previous example, instead we write an if() condition inside the test and use t.skip() if the condition is matched.

import { test } from 'node:test';
import assert from 'node:assert/strict';

// no change to the other test

test('skips programmatically if environment variable is missing', (t) => {
  if (!process.env.REQUIRED_ENV_VARIABLE) {
    return t.skip('REQUIRED_ENV_VARIABLE is missing');
  }

  assert.fail(
    'If we got here the ".failing" modifier would not be satisfied since the test passed'
  );
});

When we run this test, we get the following output:

node src/01.03-programmatic-skip.test.js
﹣ skips programmatically if environment variable is missing (0.580583ms) # REQUIRED_ENV_VARIABLE is missing
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 4.781

We’ve now seen how to skip a test programmatically from inside the test function. Next we’ll see a real-world example of programmatic skipping of node:test tests.

Programmatic skip a node:test test based on Node.js version

The real-world scenario is the following: we have a test using a Node.js API with the API contract available in Node 21 but not Node 20.

To be more precise: the MockTimers API initially supported calls of the form mock.timers.enable(['setTimeout']) in Node 20, but in Node 21 it’s been extended to also allow mocking of Date/Date.now etc. As part of this change, the mock.timers.enable() call argument shape has changed to mock.timers.enable({ api: ['Date', 'setTimeout'], now: 1000 }).

Therefore:

  • In Node 20: mock.timers.enable({}) is not supported
  • In Node 21+: mock.timers.enable({}) is supported

See also:

To recap, what we want to do is skip a test that uses mock.timers.enable(Object) on Node 20 and run it on Node 21+.

We can achieve this as follows.

First we extract the Node.js major version from process.versions.node, we want the “major” part of MAJOR.MINOR.PATCH so we .split('.')[0] to achieve this. Next we compare that MAJOR to see whether it’s less than or equal to "20", note that this is using lexicographical sort, it would be better to parse to an integer and do a number comparison.

Finally we use isNode20OrLower as the skip condition in our test, test('mock.timers can mock Date.now()', { skip: isNode20OrLower }, (t) => ....

See the full test:

import { test, mock, afterEach } from 'node:test';
import assert from 'node:assert/strict';

const getCurrentDate = () => new Date();

afterEach(() => {
  mock.timers.reset();
});

const isNode20OrLower = process.versions.node.split('.')[0] <= '20';

test('mock.timers can mock Date.now()', { skip: isNode20OrLower }, (t) => {
  t.mock.timers.enable({
    apis: ['Date'],
    now: new Date('2023-05-14T11:01:58.135Z'),
  });

  assert.deepEqual(Date.now(), 1684062118135);
});

When we run this code on Node 21 it runs:

node -v && node src/02.03-date-mock-timers.test.js
v21.5.0
✔ mock.timers can mock Date.now() (0.126667ms)
(node:4185) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
ℹ tests 1
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 10.228166

When we run this code on Node 20 the test is skipped:

node -v && node src/02.03-date-mock-timers.test.js
v20.11.0
﹣ mock.timers can mock Date.now() (0.090208ms) # SKIP
(node:4288) ExperimentalWarning: The MockTimers API is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 0
ℹ duration_ms 6.732459

We’ve now seen a real-world example of programmatic test skipping.

Author

Hugo Di Francesco

Co-author of "Professional JavaScript", "Front-End Development Projects with Vue.js" with Packt, "The Jest Handbook" (self-published). Hugo runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon, Elsevier and (currently) Eurostar.

Get The Jest Handbook (100 pages)

Take your JavaScript testing to the next level by learning the ins and outs of Jest, the top JavaScript testing library.