Writing a good unit test is not always easy when coming into a new language. It took me quite a few months to get to a place where I was happy with my node.js unit testing. I wanted my tests to be isolated, simple, robust, while mocks and stubs can easily be inserted.

You can see the finished examples on Github

I use the same modules repeatably for my tests

  • sandboxed-module - make each test isolated by running in a vm and allows you to inject mocks/stubs
  • mocha - nice test runner
  • chai - powerful assertions library
  • sinon - generate mocks and stubs easily

The file we will test:

The file we are going to test is a simple user handler which asks an api using the request library for some user details:

request = require("request")
module.exports = {
    getUserDetails: function(user_id, callback){ 
        var url = "http://www.api.com/user/" + user_id
        request.get(url, function(err, response, body){
            callback(err, body)
        })
    }
}

The setup

Using the before each of mocha we can create a brand new vm with our file running in as our test setup. The context of a beforeEach is used in the actual test so we can put things on 'this' to be used later.

SandboxedModule = require('sandboxed-module');
sinon = require('sinon');
chai = require('chai');
should = chai.should();
modulePath = "./UserHandler";

describe('User Handler', function() {
    beforeEach(function() {
        var self = this;
        // create a fake request so we don't make a real http request in our unit test
        this.request = {
            get: sinon.stub()
        }
        // get the sandboxedModule to run our file in a VM with our mocks
        return this.userHandler = SandboxedModule.require(modulePath, {
            requires: {
                "request":self.request // inject our fake http request
            }
        });
    });
}):

The first test

In our first test we can call our test file which is running in the vm, then make assersions on the sinon stub which was injected in. Here we are just testing that the called url is correct. We also need to call the callback in the request#get so the test does not get stuck. We call done once the unit test is complete.

it('should make a http request to the api', function(done) {
    var self = this;
    var user_id = "123456"
    this.request.get.callsArgWith(1) // call the request callback
    this.userHandler.getUserDetails(user_id, function(){
        //chai allows us to chain nice assertions like this
        self.request.get.calledWith("http://www.api.com/user/"+user_id).should.equal(true);
        done()
    });
});

The second test

In our second test we want to make sure the data from the http call is returned to us in the callback. We can tell the request library to use some stubbed data which we can then test against.

it('should return the user deatils from the api', function(done){
    var stubbedUserDetails = {first_name:"bob", last_name:"brown"}
    //the third argument in a request callback is the body, here we can inject stubbed data
    this.request.get.callsArgWith(1, null, null, stubbedUserDetails)
    this.userHandler.getUserDetails(user_id, function(err, userDetails){
        userDetails.should.deep.equal(stubbedUserDetails)
        done()
    });
})

Conclusion

This is the most basic pattern I have used repeatably when building nodejs applications like sharelatex.com. If you know of a nicer way or improvments please let me know in the comments.