Unit Tests in JavaScript with Sinon

Tests are today an essencial part of the software development. They help us to minimize the number of bugs.  They ease the verification that everything keeps working after code refactoring or changes. And also, among other things, they give confidence to the team, as they can be an indicator of the status of the project. There are many different types of tests: integration tests, unit tests, load tests… This entry will be focused on unit tests in JavaScript and how to use Sinon to get rid of the dependencies from another modules. We will be able to run these tests with Mocha as we saw previously.

Mocks, Spies and Stubs

As we know, unit tests are used to check how a unity of code works. This block, which we want to test, usually has dependencies on other modules. Those modules may have been written by us or not. Due to that, while creating our tests we may:

  • Need a way to check how our code interacts with the external dependencies.
  • Need  the external module to return a specific value, which may be useful for the case we are testing.
  • Want to avoid the execution of real code from those dependencies, so they don’t interfere on the results or just because we don’t want them to be run.

In JavaScript there are many options that offer a solution to these problems. One of the most popular is the library Sinon, which makes all these tasks quite easy with 3 different elements: Stubs, Spies and Mocks.

Spies in Sinon

Spy in Sinon is just a function which let us check the interactions done on it. In unit tests, it is especially useful to check how we have called an external dependency. Not only can we check if we have called a method but also there are many other options such as the number of times we called it, the arguments used… There are many other options available in their API.

In Sinon spies can be created in some different ways, though for unit testing we will usually instance them in either one of these 2 ways:

  • On anonymous functions: Especially useful to check functions received as an input in the code we want to check. The most typical example are callbacks, which are passed as a parameter and we need to check that they are called correctly inside our code. For this, we would just create the spy like this:

var mySpy = sinon.spy();

  • On functions of existing objects: Sometimes the function we want to monitor is in an object of an external dependency, in an object received as an input or in an object in the current scope. In any of these cases we have a way to generate a spy. For instance, if we want to spy the ajax function on the object jQuery it would be done in with this sentence:

var mySpy = sinon.spy(jQuery, “ajax”);

In the latter option, we have to take into account that the spy replaces the original method with a wrapped version of it, so once we don’t need it we should release the spy. The method “restore” is the one used for that. It would be done in this way:

var mySpy = sinon.spy(jQuery, “ajax”); 
// … 
// release it once we do not need it
jQuery.ajax.restore();

Now, let’s see a practical example of how to use spies. In the code shown below we have 2 services (phone_service and events_service) as well as some tests to check how they work. Every time a phone number is added, we validate it and if it is invalid we send a notification. Just before each test we are creating a couple of spies, one for console.log() and the other for emitter.emit(). We release them with restore() once we don’t need them. In the tests, we check that the spies have been used in the appropriate way by checking the “called” property (checks if it was called)  and the “calledWith” method (which also checks if it was called with some arguments).

var eventService = require('../src/events_service');

var emitter = eventService.emitter;

describe('Event service',function(done){

    beforeEach( function() {
        this.consoleSpy = sinon.spy(console, 'log');
    });

    afterEach( function() {;
        this.consoleSpy.restore();
    });

    it('Emitted errors should be logged', function() {
        emitter.emit('invalidPhone', 'a123456789');
        this.consoleSpy.called.should.be.true;
    });

});
var should = require('chai').should();
var sinon = require('sinon');

var phoneService = require('../src/phone_service');
var eventService = require('../src/events_service');

var emitter = eventService.emitter;

describe('Phone service module',function(done){

    beforeEach( function() {
        this.emitSpy = sinon.spy(emitter, 'emit');
    });

    afterEach( function() {;
        this.emitSpy.restore();
    });

    it('valid phones should not emit an error', function() {
        phoneService.setPhone('123456789');
        this.emitSpy.called.should.be.false;
    });

    it('invalid phones should emit an error', function() {
        phoneService.setPhone('a123456789');
        this.emitSpy.calledWith('invalidPhone', 'a123456789').should.be.true;
    });

});
var EventEmitter = require('events').EventEmitter;

var emitter = new EventEmitter();

emitter.on('invalidPhone', onInvalidPhone);

function onInvalidPhone(phone) {
    console.log('phone is invalid', phone);
}

exports.emitter = emitter;
var emitter = require('./events_service').emitter;

exports.setPhone = function(phone) {
    if (!/^\d+$/.test(phone)) {
        emitter.emit('invalidPhone', phone);
    }
}

Stubs in Sinon

So far, with spies we know how to check interactions with external dependencies, but we cannot modify how they work. For that we have the stubs. In Sinon a stub is a spy on which we may define its behaviour when it is called in a specific way.

This will help us in 2 different aspects:

  • It will allow us to test all possible flows within our unit of code. For example, if our code does some actions or others depending on the returned value from an external dependency, we may have several tests and for each program a stub to reply with the appropriate value to cover all possibilities.
  • It will let us avoid the execution of undesired code. Imagine that in our logic we are calling a method which is going to fail because it is not in the real environment where it should be, or a service we don’t want to use while executing our tests. A stub overwriting its behaviour on that function (replacing it with an empty function) will do the trick.

We will be able to define a stub on either an anonymous function or on an existing object. It has a similar syntax as the one used for spies, but using the “stub” keyword. Let’s see some examples of stubs:

  • Avoid the execution of the function “sendEmail()” in emailService:

var stub = sinon.stub(emailService, 'sendEmail');

  • Force the function”getUser()” in userService returns always a User with some data:
var stub = sinon.stub(userService, 'getUser')
                .returns(new User(1, 'User1'));
  • Make “getUser(id)” in userService returns 2 different users based on the parameter:
var stub = sinon.stub(userService, 'getUser');
stub.withArgs(1).returns(new User(1, 'User1'));
stub.withArgs(2).returns(new User(2, 'User2'));

There are many other options which are well documented in the API. Let’s keep on with the example code we used in the spies. Now we have a similar code but we also check if the phone is in a blacklist. That validation is done in an external service, so we will use a stub to cover the 2 different cases: be in the blacklist or not. In this way the stub should return true or false depending the test we are doing. The phone _service would be checked in this way:

var should = require('chai').should();
var sinon = require('sinon');

var phoneService = require('../src/phone_service');
var eventService = require('../src/events_service');
var blacklistService = require('../src/blacklist_service');

var emitter = eventService.emitter;

describe('Phone service module',function(done){

    beforeEach( function() {
        this.emitSpy = sinon.spy(emitter, 'emit');
        this.isInBlackListStub = sinon.stub(blacklistService, 'isInBlackList');
    });

    afterEach( function() {;
        this.emitSpy.restore();
        this.isInBlackListStub.restore();
    });

    it('valid phones not in blacklist should not emit an error', function() {
        this.isInBlackListStub.returns(false);
        phoneService.setPhone('123456789');
        this.emitSpy.called.should.be.false;
    });

    it('valid phones in blacklist should emit an error', function() {
        this.isInBlackListStub.returns(true);
        phoneService.setPhone('123456789');
        this.emitSpy.calledWith('invalidPhone', '123456789').should.be.true;
    });

    it('invalid phones should emit an error', function() {
        phoneService.setPhone('a123456789');
        this.emitSpy.calledWith('invalidPhone', 'a123456789').should.be.true;
    });

});
var EventEmitter = require('events').EventEmitter;

var emitter = new EventEmitter();

emitter.on('invalidPhone', onInvalidPhone);

function onInvalidPhone(phone) {
    console.log('phone is invalid', phone);
}

exports.emitter = emitter;
var emitter = require('./events_service').emitter;

var blacklistService = require('./blacklist_service');

exports.setPhone = function(phone) {
    if (!/^\d+$/.test(phone) ||
        blacklistService.isInBlackList(phone)) {
        emitter.emit('invalidPhone', phone);
    }
}

As we did with the spies, stubs should be released with restore() once they are not needed.

Mocks in Sinon

In addition to spies and stubs, Sinon has another element called mock which may be useful in our unit tests. A mock is a mixture between a spy and a stub, so it implements the API of both of them. But what make mocks different is that we can establish expectations on them. These expectations will be checked at the end of the test and if the mock has not been used as expected our test will fail.

The API lets define different types of expectations for mocks. After that we will use the verify() method to check if the mock was used in the right way. Mocks change a little bit the appearance of our tests, but the process keeps being more or less the same. For example, in the previous example for the stubs we could replace the stubs with mocks for blacklistService.isInBlackList. In that mock we verify that that function was called with the right parameters (i.e. the phone number). The resulting code would be:

var should = require('chai').should();
var sinon = require('sinon');

var phoneService = require('../src/phone_service');
var eventService = require('../src/events_service');
var blacklistService = require('../src/blacklist_service');

var emitter = eventService.emitter;

describe('Phone service module',function(done){

    beforeEach( function() {
        this.emitSpy = sinon.spy(emitter, 'emit');
        this.blacklistServiceMock = sinon.mock(blacklistService);
    });

    afterEach( function() {;
        this.emitSpy.restore();
        this.blacklistServiceMock.restore();
    });

    it('valid phones not in blacklist should not emit an error', function() {
        this.blacklistServiceMock.expects('isInBlackList').withExactArgs('123456789').returns(false);
        phoneService.setPhone('123456789');
        this.blacklistServiceMock.verify();
        this.emitSpy.called.should.be.false;
    });

    it('valid phones in blacklist should emit an error', function() {
        this.blacklistServiceMock.expects('isInBlackList').withExactArgs('123456789').returns(true);
        phoneService.setPhone('123456789');
        this.blacklistServiceMock.verify();
        this.emitSpy.calledWith('invalidPhone', '123456789').should.be.true;
    });

    it('invalid phones should emit an error', function() {
        phoneService.setPhone('a123456789');
        this.emitSpy.calledWith('invalidPhone', 'a123456789').should.be.true;
    });

});
var emitter = require('./events_service').emitter;

var blacklistService = require('./blacklist_service');

exports.setPhone = function(phone) {
    if (!/^\d+$/.test(phone) ||
        blacklistService.isInBlackList(phone)) {
        emitter.emit('invalidPhone', phone);
    }
}
var EventEmitter = require('events').EventEmitter;

var emitter = new EventEmitter();

emitter.on('invalidPhone', onInvalidPhone);

function onInvalidPhone(phone) {
    console.log('phone is invalid', phone);
}

exports.emitter = emitter;

To wrap up

We have seen 3 different elements which will be useful in our unit tests. Depending on the case we will have to use the one more suitable. Besides that, Sinon has many other useful features such as  fake timers (when we want to deal with operations over time) or Fake XHR for Ajax requests.

It should be noted that in all our examples we have only tested the public methods of each module. We have not tested anything directly for the private ones. We have done this like this, because we are following a pattern of black box, in which we only test our code based in the inputs it receives and the output it brings. A good code designed with a good dependency injection would help us testing the code in an easy way. Anyway, if testing public functions was not enough and you need to test private ones, then sinon would not be enough by itself, so you would need something else such as for example the library rewire.

Sometimes it is not straightforward to write unit tests. But given the benefits they bring us, we enforce you to use them. Once you get used to write them, the process will be easier. Anyway, don’t forget to complement your unit tests with other kind of tests, like integration tests with Postman or load tests with JMeter, and set up a linter so that your team write them in a similar way.

2 thoughts on “Unit Tests in JavaScript with Sinon”

Leave a Comment

¿Necesitas una estimación?

Calcula ahora