Spying with Mocha and Sinon.js

Sinon spies

Spies on sinon are redirected functions that will notify of any call to them. Let’s see an example:

    var myGreatObject = {
        myAwesomeMethod: function () {
            console.log(1); //do something
        }
    };

    it('test spies', function () {
        myGreatObject.myAwesomeMethod(); // 1

        //Now let's reconvert it into an spy:
        myGreatObject.myAwesomeMethod = sinon.spy();

        console.log(myGreatObject.myAwesomeMethod.called); // false

        myGreatObject.myAwesomeMethod();

        console.log(myGreatObject.myAwesomeMethod.called); // true
        console.log(myGreatObject.myAwesomeMethod.callCount); // 1

        myGreatObject.myAwesomeMethod();
        myGreatObject.myAwesomeMethod();
        console.log(myGreatObject.myAwesomeMethod.callCount); // 3
    });

I think that’s a good example of how spies work in Mocha+Sinon. We have an object and we can make use of it, as usual. We then redirect the object’s method to an spy, this is, the method “myAwesomeMethod” is actually a pointer, so we point to a different function, that’s all.

If we check if the spy has been called using the property “.called” it will return false as even though the method was called that happened before we had the spy in the middle. If we call the method after setting the spy and check “.called” again it will now return true. We can also call the method more times and check how the spy tracks the amount of times the method was called.

Instantiation

Apart from redirecting a method to the spy, there are two more ways to do this in Sinon as you may have other circumstances. First one is by giving the directive/method pointer as a parameter to sinon.spy(), this will return a pointer to the generated spy so you can save it on a different variable, let’s see an example:

    var myGreatObject = {
        myAwesomeMethod: function () {
            console.log(1); //do something
        }
    };

    it('instantiates spy and assigns it to diff variable', function () {
        myGreatObject.myAwesomeMethod(); // 1

        //Now let's reconvert it into an spy:
        var myLocalSpiedMethod = sinon.spy(myGreatObject.myAwesomeMethod);

        console.log(myLocalSpiedMethod.called); // false

        myLocalSpiedMethod();

        console.log(myLocalSpiedMethod.called); // true
        console.log(myLocalSpiedMethod.callCount); // 1

        myLocalSpiedMethod();
        myLocalSpiedMethod();
        console.log(myLocalSpiedMethod.callCount); // 3
    });

If you compare this example with the previous one, I think you are going to understand quite well what we are doing. Which is creating a spy by giving it which method to spy, then we give that spy to a local variable. On doing this, we avoid manipulating the real method which could cause trouble on other tests. The spy will take care of calling the real method when needed and our local spied method becomes the method that we test. Be careful with that, if you test your method using a local spy and then call the real one, the spy won’t be able to do its job.

When a method is internal

var myLocalSpy = sinon.spy(myGreatObject, "theInternalMethod");

If you find a method that you need to spy but you can’t access its pointer from the outside as it’s an internal/private one, you can use this third instantiation method to create your spy.

Unwrapping a method

As I said, you can have your local spy or spy on the real method, it all depends on your needs. That’s why you need a way to free/unwrap the real method so that other tests can work as usual:

        myGreatObject.myAwesomeMethod = sinon.spy();
        myGreatObject.myAwesomeMethod.restore()

By calling .restore() on the spy you unwrap the method removing the spy. You could use this at “afterEach()” in case you need to create an spy on too many tests.

Checking status

Apart from knowing if the method was called or the amount of times there are other things we can check or ways of checking the same thing, here are some examples:

.called
.callCount
.calledOnce
.calledTwice
.calledThrice

Leave a Reply

Your email address will not be published. Required fields are marked *