Getting Started
To define a unit test with spooning, pass a name and a function as parameters to spooning.test
.
The name usually takes the form of “Should do the thing” and the function should accept a callback and
implement the following behavior:
- to indicate a failure, callback with an error as the first parameter
- to indicate a pass, callback with a “falsy” (
false|0|undefined|null
) value as the first parameter
This should be instantly familiar to those who have used callbacks before; it is the standard convention.
Two additional wrapper functions (testSync
and testPromise
)
are provided to make it just as easy to define synchronous tests and Promise
-based tests.
In keeping with the principle that tests should look like the code they are testing, there is no special syntax unique to spooning tests.
Installing Spooning
Install spooning as a dev dependency of your project:
npm install -D spooning
Requires node 6 or later.
Creating Your First Test
Create a file named hello.test.js with the following contents:
const {test, run, exit} = require('spooning');
test('Should say hello', (callback) => {
setTimeout(() => {
callback(null, 'Hello!');
}, 100);
});
run(exit);
Congratulations! You just implemented your first unit test with spooning. It doesn’t actually check anything, but it does display a pleasant diagnostic message.
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.
Running Your First Test
node hello.test.js
This should produce the following output:
1..1
ok 1 - Should say hello
# Hello!
# test: 1
# pass: 1
# fail: 0
Wrapper Functions
The testSync
and testPromise
functions provide a way to define
synchronous and Promise
-based tests, respectively.
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
// info === { idx: 1, name: 'Should pass', diagnosticMessage: 'optional diagnostic message', error: null }
});
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', error: null }
});
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
});
Next Chapter
Continue to Writing Tests for more practical examples.