1.1.3 • Public • Published


Package Version Install Size Build Status Coverage Status Dependencies

TAP-producing test runner that is minimal but not uncivilized

Sample Output

Styled Output


npm install -D spooning

Requires node 6 or later.

A CLI is also available (which requires spooning as a peer dependency):

npm install -D spooning spooning-cli


Tests can be defined with any of the following functions:

Use whatever assertion library you like (or none at all).

Call run (and provide an optional callback) after all tests have been defined (skip calling run if you plan to use the CLI). Automatically running tests as they are defined is also supported with some limitations.

Here is a simple but complete example:

#!/usr/bin/env node

const {test, run, exit} = require('spooning');

test('Should pass (async)', (callback) => {
    setTimeout(() => {
        callback(null, 'optional diagnostic message');
    }, 100);
test('Should fail (async)', (callback) => {
    setTimeout(() => {
        callback(new Error('failed'));
    }, 100);
// Pass the provided `exit` function to exit the process with the appropriate code
//  (0 if all tests passed, 1 if not)

For simplicity's sake, this example combines the code that runs the test with the code that defines the test. This is almost never practical for a real project. See Running Tests for examples of how to organize and run tests across multiple files with or without the CLI.

If you copy the example into a file called demo.test.js, you can use the following command to run the tests:

node demo.test.js

Expected Output

ok 1 - Should pass (async)
# optional diagnostic message
not ok 2 - Should fail (async)
# ! failed
# test: 2
# pass: 1
# fail: 1


Use testPromise to define the test as a function that returns a Promise object:

const {testPromise} = require('spooning');

testPromise('Should pass (promise)', () => new Promise((resolve) => {
    resolve('optional diagnostic message');

testPromise('Should fail (promise)', () => new Promise((resolve, reject) => {
    reject(new Error('failed'));


The testSync function is provided for defining synchronous tests:

const {testSync} = require('spooning');

testSync('Should pass (sync)', () => {
    return 'optional diagnostic message';

testSync('Should fail (sync)', () => {
    throw new Error('failed');

Using Assertions

Here's an example using the native assert library provided by node:

const {ok, strictEqual} = require('assert');

testSync('Should pass', () => {

testSync('Should fail', () => {
   strictEqual('A', 'B');

You can also use assertions (and/or throw Errors) in the root scope of async tests, but otherwise errors have to be caught in the scope where they are thrown:

test('Should fail with assert (root scope)', (callback) => {
    ok(false, 'failed');

test('Should fail with assert (other scope)', (callback) => {
    setTimeout(() => {
        try {
            ok(false, 'failed');
        catch(e) {
    }, 100);

Optional Callback

An optional callback may be provided to any of the test definition functions. The callback will be called with an error if the test failed (null if it passed) and a TestResultInfo object containing the test information.

test('Should pass', (callback) => {

    setTimeout(() => {
        callback(null, 'optional diagnostic message');
    }, 100);
}, (error, info) => {    
    // error === null|undefined
    // info === { idx: 1, name: 'Should pass', diagnosticMessage: 'optional diagnostic message' }

Returning a Promise

Promise purists should note that testPromise does not automatically return a Promise. Returning a Promise that resolves/rejects when the test passes/fails is supported, but it is opt-in to avoid "unhandled promise rejection" warnings. Use promisify (available in the native util library provided by node) to wrap any of the test function calls. info is a TestResultInfo object.

const {promisify} = require('util');
const vow = promisify(testPromise);

vow('Should pass', () => new Promise((resolve) => {
    resolve('optional diagnostic message');
})).then((info) => {
    // info === { idx: 1, name: 'Should pass', diagnosticMessage: 'optional diagnostic message' }

vow('Should fail', () => new Promise((resolve, reject) => {
    reject(new Error('error message'));
})).catch((error) => {        
    // handle error

Observable Events

spooning emits events throughout the course of running the tests. See the API Documentation for more information.

const spooning = require('spooning');
spooning.on('runEnd', ({passed, total}) => {
    // passed: count of tests that passed
    // total: count of tests that ran
}).on('error', (error) => {
    // handle error

Other Examples

See test/examples for additional examples.


See the User Guide and API Documentation.


Clone the repository and run npm install in the project root.

Run Tests

Run with tape

Use the npm test command to lint the code and run the unit tests using tape. These test results are considered canonical and are used as the basis for test coverage reports.

Running the tests with tape (as opposed to with spooning itself) guards against any subtle Ouroboros-style bugs.

Run with spooning

Use the npm run t command to run the unit tests using spooning.

Usage: npm run t [-- options]

-c|--concurrency=N  # run tests in parallel, limit N (default: 10)
--bail              # exit after first test failure
--debug             # include stack trace in error output
--no-style          # TAP output will be plain text (no escape-codes)
--basic-style       # TAP output will use color escape-codes but no "icons"


npm run t -- --no-style --bail --debug -c=1

Generate Coverage Reports

Use the npm run cover command to output a text-based coverage report.

Occasionally, the text based report will show values less than 100% but not identify any offending line numbers. Use the npm run html command to output a more detailed html-based coverage report to the .coverage directory.

It should be noted that nyc is perfectly capable of generating coverage reports on tests run with spooning. The tape results are used by npm run cover to prevent any false positives that may arise when a library tests itself.

Built With


  • neo-async — uses queue from neo-async (which has no dependencies) to execute defined tests

Dev Dependencies


Fork the repo and submit a pull request. Contributions must have 100% test coverage and adhere to the code style enforced by eslint.


SemVer is used for versioning. For the versions available, see the tags on this repository.


  1. Examine what will be included in the npm bundle:

     npm run pack

    The npm run pack command requires npm version 6.4.1 or later (because it uses the --dry-run flag). For older versions of npm, run tar -tvf "$(npm pack)" to list the contents of the generated tarball.

  2. Bump the version number in package.json and create a git tag:

     npm version patch

    The npm version command accepts a SemVer argument: <newversion>|major|minor|patch (where <newversion> is a standard version number, ex. 1.0.0).

  3. Publish a new version:

     npm publish
     git push origin master --tags


Inspired by cupping


Adam Jarret


This project is licensed under the MIT License. See the LICENSE.txt file for details.



npm i spooning

DownloadsWeekly Downloads






Unpacked Size

24.2 kB

Total Files


Last publish


  • atj