Writing Tests
Let’s start with an example function to test:
const https = require('https');
function getStatus(callback)
{
const requestOptions = {
hostname: 'httpbin.org',
port: 443,
path: '/status/200',
method: 'GET'
};
// Send request
https.request(requestOptions, (response) => callback(null, response))
.on('error', callback)
.end();
}
This function makes a request to https://httpbin.org/status/200 and calls back with the response.
A unit test for this function could look like this:
const {test, UnexpectedOutputError} = require('spooning');
test('Should respond with 200', (callback) => {
getStatus((error, response) => {
// test failed if getStatus called back with error
if(error) { return callback(error); }
// test failed if getStatus called back with an unexpected status code
// (otherwise pass)
const statusError = new UnexpectedOutputError(response.statusCode, 200);
callback(statusError.actual === statusError.expected ? null : statusError);
});
});
UnexpectedOutputError
is a custom error type provided by spooning
for use in tests. Using it is entirely optional.
Avoid “Callback Hell”
Folks eager to avoid “callback hell” should note that spooning uses neo-async internally.
If installed in your module (npm i -D neo-async
), the example above could be refactored like this:
const {waterfall} = require('neo-async');
const {test, UnexpectedOutputError} = require('spooning');
test('Should respond with 200', (callback) => {
waterfall([
getStatus,
(response, cb) => cb(null, new UnexpectedOutputError(response.statusCode, 200)),
(e, cb) => cb(e.actual === e.expected ? null : e)
], callback);
});
Using Assertions
As noted in Getting Started, spooning works well with standard assertion libraries.
Here’s an example that compares two objects using deepEqual
from assert:
const https = require('https');
function getSlides(callback)
{
const requestOptions = {
hostname: 'httpbin.org',
port: 443,
path: '/json',
method: 'GET'
};
// Send request
https.request(requestOptions, (response) => callback(null, response))
.on('error', callback)
.end();
}
// ---
const {deepEqual} = require('assert');
const {test, acEx} = require('spooning');
test('Should respond with JSON', (callback) => {
const expected = {
'slideshow': {
'author': 'Yours Truly',
'date': 'date of publication',
'slides': [
{
'title': 'Wake up to WonderWidgets!',
'type': 'all'
},
{
'items': [
'Why <em>WonderWidgets</em> are great',
'Who <em>buys</em> WonderWidgets'
],
'title': 'Overview',
'type': 'all'
}
],
'title': 'Sample Slide Show'
}
};
getSlides((error, response) => {
// test failed if getStatus called back with error
if (error) { return callback(error); }
// load response body
const data = [];
response.on('data', (chunk) => data.push(chunk));
response.on('end', () => {
try {
// compare objects
const actual = JSON.parse(data.join(''));
deepEqual(actual, expected, acEx(actual, expected));
// test passed
callback();
} catch (e) {
// test failed if JSON is invalid or assertion failed
callback(e);
}
});
});
});
acEx
is a utility provided by spooning for rendering nicely formatted error messages for
actual/expected values of any type. Using it is entirely optional.
Next Chapter
Continue to Running Tests for examples of how to organize tests defined across multiple files.