SinonJS with Mocha

Setup

Fakes, spies and stubs are created in the default sandbox which needs to be restored after each test to prevent memory leaks. We use a Mocha hook plugin for that:

// Restores the default sandbox after every test
exports.mochaHooks = {
  afterEach() {
    sinon.restore();
  },
};

Types of test doubles

TypeWhat it doesBehaviorPre-programmed behaviorPre-programmed expectationsWhen to use
Fakerecords arguments, return value, the value of this and exception thrown (if any) for all of its callsimmutablenoLike stubs and spies, but simpler to use and immutable to prevent bugs
Spylike a fakemutableno
Stublike a fakemutableyesno1. To control a method’s behavior to force the code down a specific path, e.g. forcing a method to throw an error to test error handling
2. to prevent a specific method from being called directly
Mocklike a fakeyesyesshould only be used for the method under test. If you want to control how your unit is being used and like stating expectations upfront (instead of asserting after the fact). In general you should have no more than one mock in a single test
Types of test doubles in SinonJS

Fakes

Unlike sinon.spy and sinon.stub methods, the sinon.fake API knows only how to create fakes, and doesn’t concern itself with plugging them into the system under test. To plug the fakes into the system under test, you can use the sinon.replace* methods. When you want to restore the replaced properties, call the sinon.restore method.

// Using fakes instead of spies
// What it does: "replaces the method "bar" on object foo with the sinon fake"
const fake = sinon.replace(foo, "bar", sinon.fake(foo.bar));

// Using fakes instead of stubs
const fake = sinon.replace(foo, "bar", sinon.fake.returns("fake value"));

// restores all replaced properties set by sinon methods (replace, spy, stub)
sinon.restore();

Creating a fake without behavior

Same API as sinon.spy

it("should create fake without behaviour", function () {
    // create a basic fake, with no behavior
    const fake = sinon.fake();

    assert.isUndefined(fake()); // by default returns undefined
    assert.equals(fake.callCount, 1); // saves call information
});

Creating a fake with custom behavior

Fakes cannot change once created with behavior.

it("should create fake with custom behaviour", function () {
    // create a fake that returns the text "foo"
    const fake = sinon.fake.returns("foo");

    assert.equals(fake(), "foo");
});

Immutability of a Fake

Because of the immutability of a Fake, this does not work:

const readFake = sinon.fake;
// does not work: you try to modify an immutable property
sinon.yields(null, ["test.txt"]);

Instead, writing it in one line does the trick:

const readFake = sinon.fake.yields(null, ["test.txt"]);

Creating a fake from an existing function

sinon.fake(func);

Limitations of fakes

onCall() and withArgs() does not work with Fakes. A Fake must always have the same behavior. callsFake(), addBehavior() and callsThrough() do not work.

More fake API

// a fake that will throw an error
const fake = sinon.fake.throws(new Error("not apple pie"));
assert.exception(fake, { name: "Error", message: "not apple pie" });

// returns a resolved or rejected Promise for the passed value
sinon.fake.resolves(value);
sinon.fake.rejects(value);

// asserting calls
fake.calledOnce();
fake.getCall(0);
fake.calledWith("example");

Testing a NodeJS callback-style method

sinon.fake.yields([value1, ..., valueN]) returns a function that when being called, expects the last argument to be a callback and invokes that callback with the same given values. Normally used to fake a callback-style NodeJS method like readFile.

Invokes callback synchronously using fake.yields

const assert = referee.assert;
const fs = require("fs");

it("should create a fake that 'yields'", function () {
    const fake = sinon.fake.yields(null, "file content");
    const anotherFake = sinon.fake();

    sinon.replace(fs, "readFile", fake);
    fs.readFile("somefile", (err, data) => {
        // called with fake values given to yields as arguments
        assert.isNull(err);
        assert.equals(data, "file content");
        // since yields is synchronous, anotherFake is not called yet
        assert.isFalse(anotherFake.called);

        sinon.restore();
    });

    anotherFake();
});

Invokes callback asynchronously using fake.yieldsAsync

const assert = referee.assert;
const fs = require("fs");

it("should create a fake that 'yields asynchronously'", function () {
    const fake = sinon.fake.yieldsAsync(null, "file content");
    const anotherFake = sinon.fake();

    sinon.replace(fs, "readFile", fake);
    fs.readFile("somefile", (err, data) => {
        // called with fake values given to yields as arguments
        assert.isNull(err);
        assert.equals(data, "file content");
        // since yields is asynchronous, anotherFake is called first
        assert.isTrue(anotherFake.called);

        sinon.restore();
    });

    anotherFake();
});

Spies

Very much like a fake, a spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.

Creating a spy as an anon function

The spy won’t do anything except record information about its calls.

describe("PubSub", function () {
    it("should call subscribers on publish", function () {
        const callback = sinon.spy();

        PubSub.subscribe("message", callback);
        PubSub.publishSync("message");

        assertTrue(callback.called);
    });
});

Creating a spy to wrap all methods of an object

You could use sinon.spy(object) to spy on all methods of the object, but you should rather try to spy on individual methods instead using sinon.spy(object, "method"), because this will test your intent more precisely and is less susceptible to unexpected behavior, especially as the object’s code evolves.

The spy will behave exactly like the original method (including when used as a constructor), but you will have access to data about all calls.

In the following test we spy on jQuery.ajax and check if the first call receives the same url as argument that we pass in originally.

describe("Wrap existing method", function () {
    const sandbox = sinon.createSandbox();

    beforeEach(function () {
        sandbox.spy(jQuery, "ajax");
    });

    afterEach(function () {
        sandbox.restore();
    });

    it("should inspect jQuery.getJSON's usage of jQuery.ajax", function () {
        const url = "https://jsonplaceholder.typicode.com/todos/1";
        jQuery.getJSON(url);

        assert(jQuery.ajax.calledOnce);
        assert.equals(url, jQuery.ajax.getCall(0).args[0].url);
        assert.equals("json", jQuery.ajax.getCall(0).args[0].dataType);
    });
});

Observing get and set of an object property

The spies will behave exactly like the original getters and setters, but you will have access to data about all calls.

var object = {
  get test() {
    return this.property;
  },
  set test(value) {
    this.property = value * 2;
  },
};
var spy = sinon.spy(object, "test", ["get", "set"]);
object.test = 42;
assert(spy.set.calledOnce);
assert.equals(object.test, 84);
assert(spy.get.calledOnce);

More spy API

// Creates a spy that only records calls when the received arguments match those passed to withArgs.
spy.withArgs(arg1[, arg2, ...]);

spy.callCount
spy.called
spy.notCalled
spy.calledOnce
spy.calledTwice
spy.calledThrice
spy.firstCall
spy.secondCall
spy.thirdCall
spy.lastCall
spy.calledBefore(anotherSpy)
spy.calledAfter(anotherSpy)
spy.calledImmediatelyBefore(anotherSpy);
spy.calledImmediatelyAfter(anotherSpy);

// true if the spy was called at least once with obj as this
spy.calledOn(obj);

spy.alwaysCalledOn(obj);
spy.calledWith(arg1, arg2, ...);
spy.calledOnceWith(arg1, arg2, ...);
spy.alwaysCalledWith(arg1, arg2, ...);
spy.calledWithExactly(arg1, arg2, ...);
spy.calledOnceWithExactly(arg1, arg2, ...);
spy.alwaysCalledWithExactly(arg1, arg2, ...);
spy.calledWithMatch(arg1, arg2, ...);
spy.alwaysCalledWithMatch(arg1, arg2, ...);
spy.calledWithNew();
spy.neverCalledWith(arg1, arg2, ...);
spy.neverCalledWithMatch(arg1, arg2, ...);
spy.threw();
spy.threw("TypeError");
spy.threw(obj);
spy.alwaysThrew();
spy.alwaysThrew("TypeError");
spy.alwaysThrew(obj);

// Returns true if spy returned the provided value at least once.
spy.returned(obj);

spy.alwaysReturned(obj);
var spyCall = spy.getCall(n);
var spyCalls = spy.getCalls();
spy.thisValues
spy.args
spy.exceptions
spy.returnValues
spy.resetHistory();

// Replaces the spy with the original method. Only available if the spy replaced an existing method
spy.restore();

// Returns the passed format string with the following replacements performed:
spy.printf("format string", [arg1, arg2, ...]);

%n the name of the spy "spy" by default)
%c the number of times the spy was called, in words ("once", "twice", etc.)
%C a list of string representations of the calls to the spy, with each call prefixed by a newline and four spaces
%t a comma-delimited list of this values the spy was called on
%n the formatted value of the nth argument passed to printf
%* a comma-delimited list of the (non-format string) arguments passed to printf
%D a multi-line list of the arguments received by all calls to the spy

Stubs

const stub = sinon.stub().throws();
const stub = sinon.stub(object, "method");
const stub = sinon.stub(obj); // should not be used, rather stub specific methods

// Makes the stub return the provided value.
stub.returns(obj);

// Defines the behavior of the stub on the nth call. Useful for testing sequential interactions.
callback.onCall(0).returns(1);  // alias stub.onFirstCall();
callback.onCall(1).returns(2);  // alias stub.onSecondCall();
callback.returns(3);

// Makes the stub call the provided fakeFunction when invoked
stub(obj, 'meth').callsFake(fn);

// Stubs the method only for the provided arguments.
stub.withArgs(arg1[, arg2, ...]);

// Resets both behaviour and history of the stub.
stub.reset();

// Causes the stub to return the argument at the provided index or TypeError if index is unavailable.
stub.returnsArg(index);

// return a Promise which resolves/rejects to the provided value
stub.resolves(value);
stub.rejects(value);

Mocks

Expectations implement both the spies and stubs APIs. Mocks mock the entire module, not single functions.

it("should call all subscribers when exceptions", function () {
    var myAPI = { method: function () {} };

    var spy = sinon.spy();
    var mock = sinon.mock(myAPI);
    mock.expects("method").once().throws();

    PubSub.subscribe("message", myAPI.method);
    PubSub.subscribe("message", spy);
    PubSub.publishSync("message", undefined);

    mock.verify();
    assert(spy.calledOnce);
});

proxyquire

So far, we wrote our spies, stubs, fakes and mocks in the test file, but one really important question is: “How can we achieve that our ‘real’ code is using/invoking the test doubles?”. One way of achieving this would be to pass the test double as a parameter to each ‘real’ function that we want to test:

const myRealCode = require("./myRealModule");

describe('test something', () => {
  it('should do something', () => {
    const mySpy = sinon.spy();
    myRealCode.doSomething(mySpy); <-- we pass the spy so the code can invoke it
    expect(mySpy).to.have.been.called;
  })
});

But doing it that way is not really an option, because we would have to adjust all our methods. The solution is to use proxyquire module. The idea is: Instead of using require() to import your ‘real’ code into the test, you call proxyquire instead and pass in the test double that shall be used:

const proxyquire = require("proxyquire");

describe('your test suite', function () {
    it('should test something', function () {
        const mySpyOne = sinon.spy();
        const mySpyTwo = sinon.spy();
        const toTestProxy = proxyquire("../../path/to/file/that-we-want/to-test",
            {
                // key must be exactly what the import/require statement looks like in your real file
                // the value is a stub
                '../whatever/myService.js': mySpyOne,
                'jsonwebtoken': mySpyTwo
            },
        )

        toTestProxy.someMethod();
    });
});

Here is a complete example using Sinon spies:

const Crypto = require("Crypto");
const proxyquire = require("proxyquire");

it('should call Crypto to create a random token', async function() {
      const testSpy = spy(Crypto, 'randomBytes');
      const tokenHandlerProxy = proxyquire('../../src/handlers/tokenHandler', {Crypto});
      tokenHandlerProxy.requestToken();
      expect(testSpy.called).to.be.true;
});

About Author

Mathias Bothe To my job profile

I am Mathias from Heidelberg, Germany. I am a passionate IT freelancer with 15+ years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I create Bosycom and initiated several software projects.