Friday, January 30, 2015

Unit Test in Node.js application using Sinon.js test spy and stub technique

In the last few months I have written couple blogs about data structure and algorithm. It was fun! Data structure and algorithm are always fun! And I have not written any technology blog. Now today I am going to talk about how I wrote unit tests for a Node.js application I am working on during my weekends and nights.

My node.js is using express framework along with Sails' waterline ORM.  All of the model classes are exposed under global var "Model".

Here is my controller code. 

module.exports = {
    model: 'group', // If no model is specified, CRUD actions won't be inherited
    actions: {
    /**
    * create group by owner
    * 
    * @param {string}   name            group name
    * @param {string}   type            group type
    * @param {string}   project         id of the project the group belongs to optional
    * @param {string}   owner           group owner id
    */
        'post /create': function (req, res, next) {
        var userId = req.param('owner');
        var group = {
        name: req.param('name'),
        type: req.param('type'),
        project: req.param('project'),
        owner: req.param('owner'),
        users: [{id: userId}]
        };
        
        Model.group.create(group, function(err, group){
        if(err)
        return res.json({status: 'error', data: err});
        else{
        //code update user.groups with newly created group
        //should move to service class
        Model.user.findOne(userId, function(err, user){
        if(err)
    console.log("can't find user with id: " + userId);
        else{
        user.groups.push({id: group.id});
        user.save(function(err, re){
        if(err)
        console.log("update user.groups fails: " + user.id + " - " + group.id);
        });
        }
        });
        return res.json({status: 'success', data: group});
        }
        });
        
        },
}


So how I test the controller code without test Model classes? It is unit test! ;)

As Java developer I used to use JMock or EasyMock to mock out the dependencies, or write little stub classes to stub out the dependencies. In node.js world there is something similar: http://sinonjs.org/docs/#sinonspy. Sinonjs is behavior driven testing framework. And with the help of the Mocha, the unit test framework for Node.js. This blog assumes you read the introduction of Mocha and Sinonjs. 

So what I do is to stub out the Model classes and use Sinon spy to check the arguments passed back to respond obj. 

Here is my little stub here can control the behavior of waterline models.

Model.group = {
     create: function(group, callback) {callback(1, [])}
};


And another sub:

req.param = function(v) {return v;};

Now here comes the spy:

spy = res.json = sinon.spy(); 



The following is the complete test code: 

var sinon = require('sinon');
var chai = require('chai');
var assert = require('assert');
var expect = chai.expect;

var group = require('../../../api/controllers/group.js');
Model = {};

describe("Group", function() {
   describe("create group", function() {

       it("should generate error", function() {
           //my little stub here can control the behavior of waterline models.
       Model.group = {
    create: function(group, callback) {callback(1, [])}
       };
       
           var req,res, next, spy;
           req = res = next = {};
           req.param = function(v) {return v;};
           spy = res.json = sinon.spy();      
           group.actions['post /group/create'](req, res, next);
           expect(spy.calledOnce);         
           assert(spy.args[0][0].status === 'error');
           
       });
       
       it("should generate success", function() {
       Model.group = {
    create: function(group, callback) {callback(false, {name:'test'})}
       };
       
       Model.user = {
    findOne: function(user, callback) {callback(false, {groups:[], save: function(){}})}
    };
       
           var req,res, next, spy;
           req = res = next = {};
           req.param = function(v) {return v;};
           spy = res.json = sinon.spy();      
           group.actions['post /group/create'](req, res, next);
           expect(spy.calledOnce);         
           assert(spy.args[0][0].status === 'success');
           
       });

   });
});


The wonderful thing about the dynamic programming language like javascript is that the functions are properties of object and passed around as reference. Note latest Java 8's lambda expression also has function reference. To create stub becomes extreme easy as demonstrated above. 

Happy coding! 

No comments:

Post a Comment