An Introduction To Testing With Node
Testing with node can be done using the assert module.
// returns nothing!
// assert(true)
// throws an error
// assert(false)
Testing By Category
Testing could be grouped into categories:
- Truthyness
- is something true
- equality
- do 2 things equal as expected
- this could include "deep" equality
- like equality, including more "strict" assertions
- error validation
- things erro and/or throw and/or reject as expected
- failability
Assert Loosely And Strictly
Primitive Values
function addTwo(a, b) {
return a + b;
}
const ADDED = addTwo(2, 3);
// these will both pass!
assert.equal(ADDED, 5);
assert.equal(ADDED, '5');
// these FAIL
assert.strict.equal(ADDED, '5')
assert.strictEqual(ADDED, '5')
Objects
const obj = {
id: 1,
type: 'person',
name: { first: 'Joe', last: 'Schmoe' },
};
// this assert will fail because they are different objects:
assert.equal(obj, {
id: 1,
type: 'person',
name: { first: 'Joe', last: 'Schmoe' },
});
// This one also fails because the type of the primitive '1' does not match the original type
assert.deepStrictEqual(obj, {
id: `1`,
name: { first: 'David', second: 'Clements' },
})
// same as this
assert.strict.deepEqual(obj, {
id: `1`,
name: { first: 'David', second: 'Clements' },
});
// This one PASSES!
assert.deepEqual(obj, {
id: 1,
type: 'person',
name: { first: 'Joe', last: 'Schmoe' },
});
// This one ALSO PASSES!
assert.deepStrictEqual(obj, {
id: 1,
type: 'person',
name: { first: 'Joe', last: 'Schmoe' },
});
Errors Are Thrown As Expected
const assert = require('assert');
const TEST_ERR = new Error('Test String');
function addTwoNumbers(a, b) {
assert.deepEqual(typeof a, 'number', TEST_ERR);
assert.deepEqual(typeof b, 'number', TEST_ERR);
return a + b;
}
// WONT WORK because bad error matching
// assert.throws(() => {
// addTwoNumbers('1', 2);
// }, new Error('not gonna work'));
assert.throws(() => {
addTwoNumbers('1', 2);
}, TEST_ERR)
assert.throws(() => {
addTwoNumbers(1, `2`);
}, TEST_ERR);
assert.doesNotThrow(() => {
addTwoNumbers(1, 2);
}, TEST_ERR);
deepEqual
above is used to validate that arguments are, indeed, numbersthrows
is used to assert that a function will throw an Error given "bad" inputs- the first arg to
throws
here is a function, NOT the function that is tested - the tested function goes inside the function passed to
throws
- the second arg to
throws
is an error (or a string, but it is suggested not to do that). This error is compared against the error thrown from the tested function for equality. This is a nice tidbit, that a function will not only throw but will throw a specific expected error!
- the first arg to
An Interesting Error Assertion with ifError
Here, the ifError
gets used (ifError(errValue)
).
This passes when the errValue
passed to it is NOT an error, basically (when it is undefined or null). When the value passed to ifErr is, indeed, an error, the assertion fails.
Here, a mock api that includes a callback is tested. This uses the error-first paradigm of classic node apis.
//
// variables
//
const URLS = {
GOOD: 'http://laursen.tech',
BAD: 'http://unwanted.url'
}
const MY_ERR = new Error('dont use this email');
const MOCK_RETURN_STRING = 'sounds good';
//
// the mock request fn
//
const mockReq = (url, cb) => {
setTimeout(() => {
if (url === URLS.BAD) cb(MY_ERR);
else cb(null, Buffer.from(MOCK_RETURN_STRING));
}, 300);
};
//
// the tests!
//
mockReq(URLS.GOOD, (err, data) => {
assert.ifError(err);
});
mockReq(URLS.BAD, (err, data) => {
assert.deepStrictEqual(err, MY_ERR);
});
Testing Rejections
Here is a promise-based version of the above scenario. Here are two rejection-testing functions:
doesNotReject
, passing a function that is expected to NOT rejectrejects
, passing a fn that is expected to reject. This fn also takes a 2nd parameter, the Error that is expected to be rejected with
const { setTimeout: timeout } = require('timers/promises')
//
// variables
//
const URLS = {
GOOD: 'http://laursen.tech',
BAD: 'http://unwanted.url'
}
const MY_ERR = new Error('dont use this email');
const MOCK_RETURN_STRING = 'sounds good';
//
// the mock request fn
//
const mockReq = async (url, cb) => {
await timeout(300);
if (url === URLS.BAD) throw MY_ERR;
return Buffer.from(MOCK_RETURN_STRING);
};
//
// the tests!
//
assert.doesNotReject(mockReq(URLS.GOOD));
assert.rejects(mockReq(URLS.BAD), MY_ERR);