Getting better feedback from sinon stubs

DateReadtime 2 minutes Series Part 2 of Testing Express Tags

Sinon is an extremely powerful tools for writting unit tests. It helps to create standalone spies, stubs and mocks to isolate functionallity.

Setting up Sinon

The documentation suggests you can, for example, create a spy like so:

it('calls the original function', function () {
    var callback = sinon.spy();
    var proxy = once(callback);

    proxy();

    assert(callback.called);
});

Which passes just fine:

$ ./node_modules/.bin/mocha


✓ calls the original function

1 passing (7ms)

However, it doesn't give excellent feedback on failure.

$ ./node_modules/.bin/mocha


1) calls the original function

0 passing (8ms)
1 failing

1)  calls the original function:

    AssertionError [ERR_ASSERTION]: false == true
    + expected - actual

    -false
    +true

    at Context.<anonymous> (test/test-spec.js:21:5)

Getting Better Feedback

Luckily, sinon ships with a built in way of writting assertions that provide excellent feedback

sinon.assert.called(callback);

Which on failure provides the very readable:

$ ./node_modules/.bin/mocha


1) calls the original function

0 passing (9ms)
1 failing

1)  calls the original function:
    AssertError: expected spy to have been called at least once but was never called
    at Object.fail (node_modules/sinon/lib/sinon/assert.js:96:21)
    at failAssertion (node_modules/sinon/lib/sinon/assert.js:55:16)
    at Object.assert.(anonymous function) [as called] (node_modules/sinon/lib/sinon/assert.js:80:13)
    at Context.<anonymous> (test/test-spec.js:23:18)

Multple Spies

In some cases your spec might require more than one anonymous spy. Like in the following somewhat contrived example:

it('returns a user', function () {
    var req = {
        locals: {user: "a user"}
    };
    var res = {
        json: sinon.spy()
    };
    var next = sinon.spy();

    controller(req, res, next);

    sinon.assert.called(res.json);
    sinon.assert.called(next);
});

Which on failure gives the generic message:

AssertError: expected spy to have been called at least once but was never called

You can trick sinon into naming the spy or stub by passing a named function to the spy. For example:

var next = sinon.spy(function next() {});

Which gives the more useful feedback:

AssertError: expected next to have been called at least once but was never called

What's next

Sinon has lots of handy other assertions that you can leverage to write better tests. Including calledWith, calledWithMatch, and calledWithExactly.