TAP-producing test runner that is minimal but not uncivilized
Sample 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:
test
(alias:it
)testSync
testPromise
(alias:swear
)
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
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');
});
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);
});
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' }
});
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
});
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
});
See test/examples for additional examples.
See the User Guide and API Documentation.
Clone the repository and run npm install
in the project root.
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.
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
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.
Dependencies
- neo-async — uses
queue
fromneo-async
(which has no dependencies) to execute defined tests
Dev Dependencies
- eslint — enforce consistent code style
- nyc — generate test coverage reports
- tape — run unit tests (or use spooning itself)
- util.promisify — polyfill for
promisify
on node < 8
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.
-
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, runtar -tvf "$(npm pack)"
to list the contents of the generated tarball. -
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). -
Publish a new version:
npm publish git push origin master --tags
Inspired by cupping
This project is licensed under the MIT License. See the LICENSE.txt file for details.
🥄