Alexey Gravanov

Unit testing Node.js modules like a boss

Apr 12, 2015

I can call myself a full stack developer with a focus on backend. Server, network, hosting environment? No problems, doing DevOps things for a couple of years now, I'm provisioning and configuring machines, deploying and monitoring my stuff and doing infrastructure stories as a daily business. Databases and data modeling? Sure! Business logic? APIs? My precious, this is exactly where I rock the world! UI, responsive design and JavaScript-extensive pages? You're very welcome. I can do it as well. Of course, it's not possible to know all the stuff nowadays. There are always spots with a lack of knowledge and/or experience. One of mines was understanding of how it's possible to unit test Node.js modules.

Unit testing itself in JavaScript is not a problem at all. There are different unit testing frameworks around, like Jasmine, Mocha (being around for a while, well tested and maintained, it became a gold standard for Node.js), Intern or Vows. There are few mocking frameworks, like Sinon. Finally, there are tons of assertion libraries, like assert, coming with Node.js or Chai. At AutoScout24, we're using Mocha, Sinon, assert and Chai.

Node.js is leveraging module pattern. Node.js modules are like classes in all other languages, with variables, functions and exported functions. Testing such things is pretty easy if they're stand-alone modules. Assume that, there is a stuff.js module:

function doStuff() {
    var output = 42;
    // do some stuff
    return output;
}

module.exports = {
    doStuff: doStuff
};

The Node.js Way of wiring stuff is using require. Basically, require is a Node.js function for loading modules, you give module name or path for self-written modules as an input, you get a module object as an output. You can give parameters to a modules, but let's skip it for now. So, using Mocha and assert, test for stuff.js module could look like this:

var assert = require('assert');

var stuffModule = require('./stuff');

describe('stuffModule.doStuff()', function () {
    it('should return 42', function () {
        var result = stuffModule.doStuff();
        assert.equal(result, 42);
    });
});

Execute it:

$ mocha stuffSpecs.js

  stuffModule.doStuff()
    ✓ should return 42

  1 passing (6ms)

Great! But what if your module depends on another module? What if this another module works with cookies or sends Ajax requests to the underlying API? Usually, having real external dependencies is not the right idea for unit testing. This is where The Node.js Way of loading modules also known as Service Locator pattern works not that good.

In any other language, it's possible to organize the code to allow dependency injection from outside and use some IoC container to auto-wire all the things in production or inject mocked objects in the unit tests. The problem is that The Node.js Way does not allow to do such thing as a dependency injection. There are some 3rd party solutions for this problem, but most of them are either built as a replacement for a require, providing configurable Service Locator, like Intravenous or forcing you to put container-specific pieces of code in each module, like Electrolyte, making switching to something different not that easy. Spur IoC, solution from OpenTable, is quite nice, but I'm not sure if it's production ready.

Sticking with The Node.js Way for a production code, there are still few different possibilities to control require in unit tests depending on how much power and flexibility you need. Mockery gives a possibility to populate internal module cache and replace modules to be loaded with your own objects or another modules. Let's assume stuff.js module needs to read cookies:

var cookies = require('cookies-js');

function doStuff(input) {
    var output = cookies.get(input);
    // do some stuff
    return output;
}

module.exports = {
    doStuff: doStuff
};

Using Mockery, it's possible to use own stub object instead of real cookies-js module:

var assert = require('assert');
var mockery = require('mockery');

describe('stuffModule.doStuff()', function () {
    before(function () {
        var getStub = function (input) { return 42; };
        var cookiesStub = { get: getStub };
        mockery.enable();
        mockery.registerAllowable('./stuff');
        mockery.registerMock('cookies-js', cookiesStub);
    });

    after(function () {
        mockery.deregisterAll();
        mockery.disable();
    });

    it('should return 42', function () {
        var stuffModule = require('./stuff');
        var result = stuffModule.doStuff('stuff');
        assert.equal(result, 42);
    });
});

Sinon allows defining behavior of mock objects more flexible without writing any boilerplate code:

var assert = require('assert');
var sinon = require('sinon');
var mockery = require('mockery');

describe('stuffModule.doStuff()', function () {
    before(function () {
        var getStub = sinon.stub();
        getStub.withArgs('stuff').returns(42);
        getStub.withArgs('double stuff').returns(84);
        var cookiesStub = { get: getStub };
        mockery.enable();
        mockery.registerAllowable('./stuff');
        mockery.registerMock('cookies-js', cookiesStub);
    });

    after(function () {
        mockery.deregisterAll();
        mockery.disable();
    });

    it('should return 42 for stuff', function () {
        var stuffModule = require('./stuff');
        var result = stuffModule.doStuff('stuff');
        assert.equal(result, 42);
    });

    it('should return 84 for double stuff', function () {
        var stuffModule = require('./stuff');
        var result = stuffModule.doStuff('double stuff');
        assert.equal(result, 84);
    });
});

If even more power is needed, there is an another great mocking tool - Rewire. Rewire adds getter and setter for the module internals, allowing to replace loaded module objects (original modules still will be loaded in this case) with mock objects, get or set private variables, replace or modify old functions or even inject new code:

var assert = require('assert');
var sinon = require('sinon');
var rewire = require('rewire');

describe('stuffModule.doStuff()', function () {
    var cookiesStub;

    before(function () {
        var getStub = sinon.stub();
        getStub.withArgs('stuff').returns(42);
        getStub.withArgs('double stuff').returns(84);
        cookiesStub = { get: getStub };
    });

    it('should return 42 for stuff', function () {
        var stuffModule = rewire('./stuff');
        stuffModule.__set__('cookies', cookiesStub);
        var result = stuffModule.doStuff('stuff');
        assert.equal(result, 42);
    });

    it('should return 84 for double stuff', function () {
        var stuffModule = rewire('./stuff');
        stuffModule.__set__('cookies', cookiesStub);
        var result = stuffModule.doStuff('double stuff');
        assert.equal(result, 84);
    });
});

As always, with great power comes great responsibility, modifying private scope of a tested module a lot may change original behavior resulting in useless unit tests testing faked behavior. Keep that in mind choosing mocking tool for testing Node.js modules.