Mocking dependencies when testing in NodeJS

Software Dependencies. Love them or hate them, a project usually has at least one chunk of code, internal or 3rd party, that it needs to have to operate properly - the fuel to the machine if you will.

When it comes down to testing, you'll need to manage these dependencies and one approach of doing this is to mock them. Replacing the core functionality provided by the framework, language, or in this context, NodeJS the platform, with 'something else'.

This 'something else' can be anything - a noop (No Operation - an empty function), a function that returns a hardcoded value or something more comprehensive. The code being tested will then use the mocked chunk of code and be none the wiser that it's interacting with an imposer. These all move the developer closer to the main goal - testing program output from a given input.

For NodeJS, there are two main packages that I've used (has to be more there though) for mocking dependencies. Proxyquire and Rewire. Both of these packages do similar things and both allow easy mocking of private variables. Simply because I prefer rewire's API, I'll be using that in examples below.

Mocking with Rewire

It's really easy to start mocking a module with Rewire.

var rewire = require("rewire");

//rewire takes the place of require
var module = rewire("./index");

//Set the value of privateVar
module.__set__("privateVar", "mockedValue");

//Get the value of privateVar
module.__get__("privateVar");  

Here, the require you know and love in NodeJS is replaced with rewire, doing the same job but exposing a set of extra functions - with two of these being __set__ and __get__. Here the variable privateVar value is being changed to the string "mockedValue". The variable being mocked and the value it's being changed to can be of any type. A full list of the Rewire API can be found Github.

Taking this idea further:

var module = rewire("./index");

/**
 * Mock process.exit() function
 *
 * Stop actually exiting the process and
 * cancelling the test runner
 */
module.__set__("process", {  
    exit: function () {}
});

/**
 * Set paths that may otherwise be set
 * based of the current working directory
 */
module.__set__("paths", {  
    cwd: '/path/to/new/cwd',
    root: '/path/to/new/root'
})

Vitally, it's important to note that anything set with the __set__ only affects the scope of the module. This may sound obvious for private variables, such as any dependencies, but it also means any changes to functions such as console.log or process.exit as shown above will not affect uses of it elsewhere - be it separate files or the test runner itself.

You could take the idea further once more and take the input, store it, to then assert if the error code is what is being expected. An example of this in action can be seen further down.

Making your own mockery

If you need more control or want to use a mocked module to check program behaviour, you can make a mock yourself. Compared to using one of the tools mentioned above, one that you create yourself has limitations such as it will only work for global functions (such as process.exit()) and will affect any use of the mocked function (such as the test runner) until the original is restored.

Below is how process.exit can be mocked.

var mockProcessExitApi = {};

var _originalProcessExit;  
var _hasBeenCalled = false;  
var _errorCode;

mockProcessExitApi.enable = function() {  
    _originalProcessExit = process.exit;

    process.exit = function(errorCode) {
        _errorCode = errorCode;
        _hasBeenCalled = true;
    }
}

mockProcessExitApi.callInfo = function() {  
    return {
        called: _hasBeenCalled,
        errorCode: _errorCode
    };
}

mockProcessExitApi.restore = function() {  
    process.exit = _originalProcessExit;
    _hasBeenCalled = false;
    _errorCode = null;
}

module.exports = mockProcessExitApi;  

Here the original function is stored internally before being replaced with a mocked function. This allows the original to be restored when needed.

The three public functions do the following:

  • enable - Replace the native functionality with mocked functionality, not before storing the original function in _originalProcessExit. Also set is the internal state on whether it has been called and what the error code was.
  • callInfo - Function that returns a object with keys called boolean and errorCode integer (though strictly this is whatever is passed by your program when it exits).
  • restore - Reinstate the original function and resetting internal state variables.

This approach is admittedly more verbose than using something like Rewire, though this may give more flexibility.

Now, wherever we are testing code the exits the process we can enable the mock function which will store if it has been called and with what error code. Armed with this data we can, if needed, assert against process.exit being called and with a specific error. Once we're finished we can run restore, switching back in the original, un-mocked function. This means behaviour further into program flow is not affected (such as if any future tests fail themselves).

A way of doing this could be as follows:

var callInfo;  
var mockProcessExit = require('./mocks/mockProcessExit');

before(function() {  
    mockProcessExit.enable();
    functionThatIsBeingTested();
    callInfo = mockProcessExit.callInfo();
});

after(function() {  
    mockProcessExit.restore();
})

it('should exit the process', function() {  
    expect(callInfo.called).to.be.true;
})

it('should exit with an error code 0 (no error)', function() {  
    expect(callInfo.errorCode).to.equal(0);
})

Whether you're using an 'off npm' solution or doing your own, mocking out NodeJS is simpler than you may think and can help increase test effectiveness.