spooning

TAP-producing test runner that is minimal but not uncivilized

Usage no npm install needed!

<script type="module">
  import spooning from 'https://cdn.skypack.dev/spooning';
</script>

README

spooning

Package Version Install Size Build Status Coverage Status Dependencies

TAP-producing test runner that is minimal but not uncivilized

Sample Output

Styled Output

Install

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

Examples

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)
run(exit);

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

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

testPromise

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'));
}));

testSync

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', () => {
   ok(true);
});

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');
    callback();
});

test('Should fail with assert (other scope)', (callback) => {
    setTimeout(() => {
        try {
            ok(false, 'failed');
            callback();
        }
        catch(e) {
            callback(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.

Documentation

See the User Guide and API Documentation.

Development

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"

Example

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

Dependencies

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

Dev Dependencies

Contributing

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

Versioning

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

Releasing

  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
    

Acknowledgments

Inspired by cupping

Author

Adam Jarret

License

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



🥄