Unit test Node code in 10 seconds

Use Mocha and the package script commands to quickly start unit testing CommonJS code.

Unit testing CommonJS code under Node has never been easier. Here is how to start unit testing your package in 10 seconds. Let us write a small module to add two numbers

add.js
1
2
3
module.exports = function add(a, b) {
return a + b;
};

Assuming we already have package.json, do the following (you can copy and paste the commands directly from the blog post)

second 1

Install the Mocha testing framework, save it under devDependencies

npm install --save-dev mocha

I love Mocha and recommend it for JavaScript testing if you like Behavior-Driven Design (BDD) testing pattern.

second 2

Create a folder for the test files (specs) and add first empty file

md test
git touch add-spec.js

second 3-5

Paste the BDD boilerplate into the add-spec.js file

1
2
3
4
5
6
7
8
9
describe('add', function () {
var add;
beforeEach(function () {
add = require('../add');
});
it('is a function', function () {
console.assert(typeof add === 'function');
});
});

second 6

Run mocha on npm test command by setting the command in the package.json

1
2
3
"scripts": {
"test": "mocha -R spec test/*-spec.js"
}

I prefer the spec runner, since it creates nice hierarchy of passing / failing unit tests

second 7

Execute the tests

npm test

Produces the following output

$ npm test
> [email protected] test /js/mocha/add
> mocha -R spec test/*-spec.js
  add
    ✓ is a function
  1 passing (5ms)

Beautiful!

Bonus: second 8

It would be nice if the tests were rerun automatically as we were changing the source code. Mocha has built-in watch feature, I prefer creating a separate npm script command to enable it (passing command line options using -- separator requires Node > 0.11)

1
2
3
4
"scripts": {
"test": "mocha -R spec test/*-spec.js",
"watch": "npm test -- --watch"
}

To run the unit tests and watch for file changes use npm run watch

Bonus: second 9-10

Unlike Jasmine, Mocha does not come with built-in matchers. In the example above I used the simple console.assert that throws an exception if the predicate is false. For more power I include my lazy-ass assertion function together with check-more-types predicate collection.

npm install --save-dev lazy-ass check-more-types

The same spec file will look like this

1
2
3
4
5
6
7
8
9
10
11
require('lazy-ass');
var check = require('check-more-types');
describe('add', function () {
var add;
beforeEach(function () {
add = require('../add');
});
it('is a function', function () {
la(check.fn(add), 'expected add function', add);
});
});

If the predicate fails, la (short alias to lazyAss) will intelligently stringify all arguments, making sure the failure context is available.

Bonus: second 11-14

To rerun unit tests on each commit to avoid breaking the local master just install pre-git and connect it to the npm test command

npm install --save-dev pre-git
package.json
1
2
3
4
5
"scripts": {
"test": "mocha -R spec test/*-spec.js",
"watch": "npm test -- --watch"
},
"pre-commit": "npm test"

Every commit will now run npm test and only commit if the unit tests pass.

Bonus: second 15-25

Running unit tests locally is nice, but making sure they run on a separate continuous integration machine is even nicer. If your project is open source and hosted on Github, enable TravisCI integration by going to your profile page https://travis-ci.org/profile/ and toggling the checkbox for the repo (if the repo is less than 1 day old you will need to click "Sync" button first).

Add the Travis config file .travis.yml to your repo

1
2
3
4
5
6
7
language: node_js
node_js:
- "0.10"
- "0.12"
branches:
only:
- master

The Mocha tests will be rerun on each push to the Github, as well as any pull requests. To let everyone know the tests are there and passing, add a badge to the README.md file

[![Build status][ci-image]][ci-url]
[ci-image]: https://travis-ci.org/<username>/<project name>.png?branch=master
[ci-url]: https://travis-ci.org/<username>/<project name>

Substitute your own Github username and the repo's name

Bonus: seconds 25-35

BDD specs have too much boilerplate - everything is a function, which is verbose in ES5. Luckily we can easily write the unit tests in ES6 and use arrow notation to avoid extra characters

Add Babel preprocessor to the Mocha command

1
npm install --save-dev babel
package.json
1
2
3
4
"scripts": {
"test": "mocha -R spec --compilers js:babel/register test/*-spec.js",
"watch": "npm test -- --watch"
}

Now modify test/add-spec.js to use arrow functions, the tests and assertions become one-liners

1
2
3
4
5
6
7
8
require('lazy-ass');
var check = require('check-more-types');
describe('add', () => {
var add;
beforeEach(() => add = require('../add'));
it('is a function', () =>
la(check.fn(add), 'expected add function', add));
});

Note: Babel 6 has changed the instructions, and now requires installing a transformation plugin, and creating a .babelrc file. Read the instructions

Bonus: seconds 35-40

You can add snapshot testing to any JS framework using snap-shot library. All you need is to install and start using it.

1
npm install --save-dev snap-shot
1
2
3
4
5
const snapshot = require('snap-shot')
it('compares complex objects', () => {
const result = {...} // from somewhere
snapshot(result)
})

The first time the test runs, it saves the object result as a JSON file. You should add this JSON file to the Git repo. Next time the test runs, it will compare the new result to the saved object and raise an error if they differ. Read more about snapshot testing.

hint: for highly dynamic data you can snapshot object schema using schema-shot or some other snapshot testing lib

Updates

Pick snapshot library

There are multiple unit testing libraries, I wrote some thoughts on how to pick one.

CoffeeScript

You might consider writing unit tests in a language other than JavaScript. For example CoffeeScript is a language that compiles into JavaScript on the fly via Node require hook. Loading .coffee files into Mocha and transpiling them is very simple. For example

math.js
1
2
3
4
5
6
function add(a, b) {
return a + b
}
module.exports = {
add
}

and the spec file

spec.coffee
1
2
3
4
add = require('./math').add
describe 'addition', ->
it 'adds numbers', ->
console.assert (add(2, 3) == 5)

Install mocha and coffeescript first

1
$ npm i -D mocha coffeescript

and register CoffeeScript require hook

package.json
1
2
3
4
5
{
"scripts": {
"test": "mocha -r coffeescript/register spec.coffee"
}
}

CoffeeScript tests now run.

1
2
3
4
5
$ npm t
> mocha -r coffeescript/register spec.coffee
addition
✓ adds numbers
1 passing (10ms)

Perfect!

Alternatively, you can specify that CoffeeScript can only transpile .coffee files

package.json
1
2
3
"scripts": {
"test": "mocha --compilers coffee:coffeescript/register spec.coffee"
}

The result is the same.

Use Mocha options file

If there are a lot of CLI options to pass to the Mocha runner, you can place them into a file. For example, create a folder test and add a file mocha.opts there with CoffeeScript compiler options.

1
2
3
4
5
6
7
$ ls
math.js
node_modules/
package.json
test/
mocha.opts
spec.coffee
1
2
3
4
$ cat test/mocha.opts
test
--compilers coffee:coffeescript/register
--recursive

And now we can use simple test command in package.json because the test folder "test" and the Mocha options are all going to be found automatically!

package.json
1
2
3
4
5
{
"scripts": {
"test": "mocha"
}
}

TypeScript

You can write your tests using TypeScript (see my intro to TypeScript blog post). Let us install the TS compiler and bring type definitions for Mocha right away. I will also add ts-node that has TypeScript require hook. Then we will initialize a TypeScript configuration (if the project already does not have one). We will need to set the allowJs option to true in the tsconfig.json for TypeScript spec files to be able to load math.js file.

1
2
$ npm i -D ts-node typescript @types/mocha
$ $(npm bin)/tsc --init

Let us write simple TypeScript spec file

test/add-spec.ts
1
2
3
4
5
6
import {add} from '../math'
describe('math/add', () => {
it('concatenates strings', () => {
console.assert(add('foo', 'bar') === 'foobar')
})
})

Add TypeScript compiler to the Mocha options file

test/mocha.opts
1
2
3
test
--compilers coffee:coffeescript/register,ts:ts-node/register
--recursive

and enjoy CoffeeScript and TypeScript specs running!

1
2
3
4
5
6
7
$ npm t
> mocha
math/add
✓ concatenates strings
addition
✓ adds numbers
2 passing (9ms)

Perfect.