If you’re unit testing Javascript use Sinon.js, it’s more useful than you expect

For a long time I didn’t use any unit testing libraries with Javascript. After all unlike Java you can do anything with your objects. If you want your car to be a cat that can walk you simply modify the object directly. So what’s the point of having a mocking library?

I discovered sinon.js a few months ago and immediately fell in love. It made me realize how much useless boilerplate code I had in my unit tests and immediately helped me write cleaner, more elegant code.

Here’s a very basic example of how I used to mock functions before and after sinon:

/** Before sinon.js **/

getAnimationSheetArgs = null;
impactMock.game.cache.getAnimationSheet = function() {
getAnimationSheetArgs = arguments
};
bullet.loadAnimations();
assert(getAnimationSheetArgs != null);
assert.equal(getAnimationSheetArgs[0], "img/bullets/awesome.png");
assert.equal(getAnimationSheetArgs[1], 5);
assert.equal(getAnimationSheetArgs[2], 15);
/** After sinon.js **/

impactMock.game.cache.getAnimationSheet = sinon.spy();
bullet.loadAnimations();
assert(impactMock.game.cache.getAnimationSheet.calledWith("img/bullets/awesome.png", 5, 15));

Before Sinon I had to declare a variable to hold the arguments passed to each function I wanted to mock. After using sinon this became one line and verifying the correct arguments were passed to the function can be done in one function call.

It may not look like much, but when you have over 1000 unit tests (as Tower Storm now has) it adds up to a lot of time saved.

Sinon also provides tons of functionality to stub out functions. You can make a method automatically return certain values or even call a callback with specific arguments (for testing async code).

Let’s say you want to test a render function to ensure user details are displayed. It looks like this:

var AdminController = {
userInfo: function (req, res) {
userId = req.param('id');
User.findById(userId, function (err, user) {
if (err) return res.send(500);
res.jsonp(200, user.data);
});
}
}

Now we only want to test that user.data is being sent to the browser. We don’t want to actually hit the database and find a user with findById so we need to mock it out.

it("Should send user.data to the browser", function () {
var mockUser = {data: {name: 'test'}};
sinon.stub(User, 'findById').callsArgWith(1, null, mockUser);
req = {param: sinon.stub().returns(123)};
res = {jsonp: sinon.spy()};
AdminController.userInfo(req, res);
assert(res.jsonp.calledWith(200, {name: 'test'}));
User.findById.restore()
});

On line 1 we create a mock user which we want to display. Then we stub the User.findById method to instantly call argument 1 (the callback) with the 2 arguments null and mockUser (for it’s err and user arguments).
On line 3 we create req as an object with just the param method. We set param to a sinon stub and make it instantly return 123 (the user’s id, although it can return anything as User.findById doesn’t even use it).
On line 4 we create res as an object that only has a jsonp method. We set this method to be a sinon spy as it doesn’t need to return anything, it only needs to record what it was called with.
On lines 5 and 6 we call the method and check that res.jsonp was successfully called with the users data using sinons handy calledWith function.
Finally on line 7 we call restore on User.findById to remove the stub and restore it’s original functionality. This is so if we have tests in the future that want to use the original function they won’t break unexpectedly.

This is by far the easiest way I’ve found to mock and unit test javascript though if you know of a better way let me know. I’m always trying to be as efficient as possible.

 

tim