Home

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, numbers
  • throws 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!

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 reject
  • rejects, 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);
Tags: