Wednesday, April 25, 2012

JSLint: don't make functions within a loop

Interestingly got this JSLint warning "don't make functions within a loop" today, scratched my head a bit and realized it might be due to the fact that function inside a loop is very prone to cause errors. You are expecting to get different values for the collections you are looping through in each iteration, but it ended up all the same as the value from the last iteration. This post explains it well with a straightforward example.

Another post suggests having functions inside the loop actually cause performance degrade as well, quite interesting experiments.

Mockery: easy mocking in Node.JS

Many JavaScript mocking frameworks are not specifically designed for Node.JS, so when we need to mock built-in Node.JS modules or npm modules, things get quite ugly and hacky.

One approach is to expose __module from module and replace the things you want to test with the mocks. I am not sure if this would work for the built-in modules and it is quite dangerous to mess with __module. Here is the example borrowed from this StackOverflow post:

// underTest.js
var innerLib = require('./path/to/innerLib');

function underTest() {
    return innerLib.toCrazyCrap();
}

module.exports = underTest;
module.exports.__module = module;

// test.js
function test() {
    var underTest = require("underTest");
    underTest.__module.innerLib = {
        toCrazyCrap: function() { return true; }
    };
    assert.ok(underTest());
}


Another approach is to use Dependency Injection (DI). Basically, you pass in mock object through a overwritten module.exports. This post explains well with an example that I borrow below. But do you really want to change the way you invoke require? How about when you are using someone else's library that you cannot even change? What if there are many mock objects you need to pass in?

// overwrite the require with one that accepts a mock object
module.exports = function(http) {
  var http = http | require('http');
  // private functions and variables go here...

  //return the public functions
  return {
    twitterData: function(callback) {
     http.createClient(...etc...);
    }
  };
}

// use it *normally*
var twitter = require('twitter')();

// use it in the test
var mockHttp = { createClient: function() { assert(something); } };
var twitter = require('twitter')(mockHttp);
//do some tests.


So, finally, a more practical solution needs to modify how Node looks up and loads modules. We initially were using some hacks like these:

// Mock native modules, e.g. http
var mockHttp = { request : function () {} };
require.cache['http'] = { exports: mockHttp };

// Mock non-native modules
var path = './test';
var absPath = require.resolve(path);
var mockTest = {...};
require.cache[absPath] = { exports : mockTest };

But this is not always reliable, you need to pay attention to the order you overwrite the modules, be careful with nested require, also it is difficult to reuse the mock objects between different tests, etc.


Finally, a colleague Martin Cooper implemented an elegant solution that makes unit testing with mock objects easy as a breeze. It is called Mockery.

- It supports nested require cases.
- It gives warning about modules that are not mocked out (you can use registerAllowable if you are sure you don't need to mock those out).
- It manages life cycle of mock objects cleanly. You can easily use different mock objects for the same module in different tests (Node only loads a module or mocked module once throughout the process and Mockery can help clean it up).

Here is a quick example how to use it (with YUITest, but you can use Mockery with any testing framework of your choice).

- YUITest can be installed through "npm install yuitest".
- Mockery is installed through "npm install mockery".
- To run the test, do "node node_modules/yuitest/cli.js fsclient.test.js".

///////////////////
//fsclient.js
///////////////////
var fs = require('fs');

function getDate() {
    var today = new Date();
    return today.toUTCString();
}

function getFileContent(filename, callback) {
    fs.readFile(filename, function (err, content) {
        if (err) {
            callback(err);
        } else {
            callback(null, getDate() + "\n" + content);
        }
    });
}
module.exports.getFileContent = getFileContent;
///////////////////
//fsclient.test.js
///////////////////
var YUITest = require('yuitest');
var Assert = YUITest.Assert;
var TestCase = YUITest.TestCase;
var mockery = require('mockery');
var sut = '../fsclient';
var client;

var fsMock = {
 readFile: function (filename, callback) {
        if (filename === 'error') {
            callback(
                new Error('error reading file: ' + filename)
            );
        } else {
            callback(null, 'file content: hello!');
        }
    }
};

var tc = new TestCase({
    'name': 'demo yuitest testcase for fs mocking',

    setUp: function () {
        mockery.enable();
        //replace fs with our fsMock
        mockery.registerMock('fs', fsMock);
        //explicitly telling mockery using the actual fsclient is OK
        //without registerAllowable, you will see WARNING in test output
        mockery.registerAllowable('../fsclient');
    },

    tearDown: function () {
        mockery.deregisterAll();
        mockery.disable();
    },

 testGetFileContentError: function () {
        client = require(sut);
        client.getFileContent('error', function (err, content) {
            Assert.isInstanceOf(Error, err);
            Assert.isTrue(err.message.indexOf('error reading file') !== -1);
        });
    },

    testGetFileContentSuccess: function () {
        client = require(sut);
        client.getFileContent('success', function (err, content) {
            Assert.isNull(err, 'should not get error');
            Assert.areSame((new Date()).toUTCString() + "\nfile content: hello!", content);
        });
    }
});

YUITest.TestRunner.add(tc);
YUITest.TestRunner.run();

There are several other Node.JS mocking frameworks like node-sandboxed-module, injectr, which also worth taking a look.


Martin also has another weapon called Sidedoor, which exposes the private functions that are not exposed to the public. It really helps thoroughly testing the code and improve your code coverage. When your boss tells you code coverage needs to be 90%+ and some error branches are really hard to mock, what do you do?

Use Mockery+Sidedoor!


Other references:

- "Testing private state and mocking dependencies" by Vojta Jina
- "Mockery: hooking require to simplify the use of mocks" discussion thread on Node.JS group
- YUITest, now supporting Node.JS testing as well, also provides yuitest-coverage tool that generates code coverage reports to integrate with Hudson/Jenkins CI environment
- Node.JS module: exports v.s. module.exports