SkillAgentSearch skills...

ZUnit

A zero dependency, non-polluting, low magic, test harness for Node.js

Install / Use

/learn @acuminous/ZUnit
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

zUnit

Node.js CI NPM version NPM downloads Maintainability Test Coverage Discover tripitaka

TL;DR

zUnit = goodbits(tape) + goodbits(mocha) - dependencies;

Index

About

zUnit is a zero dependency<sup>1</sup>, non-polluting<sup>2</sup> test harness for Node.js that you can execute like any other JavaScript program. I wrote it because mocha, my preferred test harness, is the number one culprit for vulnerabilities in my open source projects and I'm tired of updating them just because mocha, or one of its dependencies triggered an audit warning. While zUnit does lack some of the advanced features, such as concurrent tests, automatic retries and true file globbing<sup>3</sup>, most of the day-to-day features are present.

1. zero-dependency

zUnit has no production dependencies, but does depend on a few development dependencies such as eslint and prettier.

2. non-polluting

You can add test functions (describe, it, etc) to the global namespace via the pollute config option.

3. advanced-features

Since writing zUnit I've begun to wonder whether some of Mocha's advanced features are universally beneficial. Many test suites are too small to warrant concurrency, and others (e.g. persistence tests) may require a great deal of effort to isolate. Concurrent testing also has drawbacks - the test harness and reporters become more complex and the output must be buffered, delaying feedback. I'm also unconvinced about automaticaly retrying tests, I think it better to fix any that are flakey, and take a statistical approach when results are naturally unpredictable.

Usage

  1. Install zUnit

    npm i zunit --save-dev
    
  2. Add the zUnit script to package.json

    {
      "scripts": {
        "test": "zUnit"
      }
    }
    
  3. Create a test suite, e.g. test/user-db.test.js

    const { describe, it, xit, beforeEach } = require('zunit');
    const assert = require('assert');
    const userDb = require('../lib/user-db');
    
    describe('User DB', () => {
      beforeEach(async () => {
        await userDb.flush();
      });
    
      describe('List Users', () => {
        it('should list all users', async () => {
          await userDb.create({ name: 'John' });
          await userDb.create({ name: 'Julie' });
    
          const users = await userDb.list();
          assert.strictEqual(users.length, 2);
          assert.strictEqual(users[0].name, 'John');
          assert.strictEqual(users[1].name, 'Julie');
        });
    
        xit('should list matching users', async () => {});
      });
    });
    
  4. Run the tests

    npm test
    
    User DB
      List Users
        should list all users
         - PASSED (2ms)
        should list matching use
         - SKIPPED (0ms)
    
    Summary
      Tests: 2, Passed: 1, Skipped: 1, Failed: 0, Duration: 2ms
    
    

Breaking Changes

4.0.0

Suite.discover() was made asynchronous to support dynmically importing ECMAScript modules. If you used a custom launch script that automatically discovers tests you will need to update it to wait for discover to resolve. Another breaking side-effect of this change is that it is no longer possible to implicitly export test suites. Previously you could require test suites that were defined with the describe syntax as follows...

describe('Database Tests', () => {
  // ...
});
const databaseTests = require('./databaseTests.test.js');

const allTests = new Suite('All Tests').add(databaseTests);

Now if you manually compose test suites rather than discovering them, they must be explicitly exported, e.g.

module.exports = describe('Database Tests', () => {
  // ...
});

Configuration

You can configure zUnit's launch script by:

  1. Specifying a configuration file when invoking the script, e.g.
    {
      "scripts": {
        "test": "zUnit test/zUnit.json"
      }
    }
    
  2. Adding a zUnit subdocument to package.json, e.g.
    {
      "zUnit": {
        "exit": true,
        "pollute": true,
        "require": ["test/setup.js"]
      }
    }
    
  3. Creating a file called .zUnit.json or .zUnit.js in the project root

Configuration options

| Name | Type | Default | Notes | | --------- | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------ | | name | String | package.name | The top level suite name. | | directory | String | path.resolve('test') | The initial directory to recurse when requiring tests. | | pattern | String or RegExp | /^[\w-]+\.test\.js$/ | The regular expression to use for matching test files. Omit the start and end slashes when using json | | require | Array | [] | A list of CommonJS or ECMAScript modules to load before discovering tests. | | pollute | Boolean | false | Control whether to pollute the global namespace with test functions so you don't have to require them. | | exit | Boolean | false | For the node process to exit after tests are complete. |

Testing

Callbacks

Sometimes the code under test uses callbacks, making it easier if the test is callback based too. If you define your test functions to take two arguments, the second argument will be passed a callback which you should invoke to signify that the test is done. e.g.

const { describe, it } = require('zunit');

it('should do something wonderful', (test, done) => {
  callbackApi((err, items) => {
    if (err) return done(err);
    assert.strictEqual(items.length, 0);
    done();
  });
});

Unlike with mocha, you can make the test function asynchronous, allowing you to use await when you have a mixture of callback and promise based code in your test.

Pending / Skipping Tests

You can define pending tests / skip tests in the following ways...

  1. Using xit

    const { describe, xit } = require('zunit');
    
    describe('My Suite', () => {
      xit('should do something wonderful', async () => {
        // ...
      });
    });
    
  2. Passing an option to it

    const { describe, it } = require('zunit');
    
    describe('My Suite', () => {
      it(
        'should do something wonderful',
        async () => {
          // ...
        },
        { skip: true, reason: 'Optional Reason' }
      );
    });
    
  3. Using xdescribe

    const { xdescribe, it } = require('zunit');
    
    xdescribe('My Suite', () => {
      it('should do something wonderful', async () => {
        // ...
      });
    });
    
  4. Passing an option to describe

    const { describe, it } = require('zunit');
    
    describe(
      'My Suite',
      () => {
        it('should do something wonderful', async () => {
          // ...
        });
      },
      { skip: true, reason: 'Optional Reason' }
    );
    
  5. Defining a test without a test function

    const { describe, it } = require('zunit');
    
    describe('My Suite', () => {
      it('should do something wonderful');
    });
    
  6. Returning test.skip() from within a test function

    const { describe, it } = require('zunit');
    
    describe('My Suite', () => {
      it('should do something wonderful', async (test) => {
        return test.skip('Optional Reason');
      });
    });
    
  7. In a beforeEach hook

    const { describe, it, beforeEach } = require('zunit');
    
    describe('My Suite', () => {
      beforeEach(async (hook) => {
        return hook.test.skip('Optional Reason');
      });
    
      it('should do something wonderful', async (test) => {
        // ...
      });
    });
    
  8. In a before hook

    const { describe, it, before } = require('zunit');
    
    describe('My Suite', () => {
      before(async (hook) => {
        return hook.suite.skip('Optional Reason');
      });
    
      it('should do something wond
    

Related Skills

View on GitHub
GitHub Stars4
CategoryDevelopment
Updated7mo ago
Forks2

Languages

JavaScript

Security Score

77/100

Audited on Aug 25, 2025

No findings