Sinon.js documentation

This documentation below is an adaptation of the official Sinon.js documentation.

Sinon.js is included in Unit.JS, you can use Sinon.js with Unit.js.

Load Unit.js :

var test = require('unit.js');

Utils for the examples of this documentation :

// some functions

function once(fn) {

  var returnValue;
  var called = false;

  return function() {
    if (!called) {
      called = true;
      returnValue = fn.apply(this, arguments);
    }
    return returnValue;
  };
};

var jQuery = {
  ajax: function() {}
};

function getTodos(listId, callback) {
  jQuery.ajax({
    url: '/todo/' + listId + '/items',
    success: function(data) {

      // Node-style CPS: callback(err, data)
      callback(null, data);
    }
  });
}

Spy

var spy = test.spy();

spy(42);

test.assert(spy.withArgs(42).calledOnce);

Calls the original function :

var callback = test.spy();
var proxy = once(callback);

proxy();

test.assert(callback.called);

Calls the original function only once :

var callback = test.spy();
var proxy = once(callback);

proxy();
proxy();

test.assert(callback.calledOnce);

// ...or:
test.assert.strictEqual(callback.callCount, 1);

Calls original function with right this and args :

var callback = test.spy();
var proxy = once(callback);
var obj = {};

proxy.call(obj, 1, 2, 3);

test.assert(callback.calledOn(obj));
test.assert(callback.calledWith(1, 2, 3));

Stubs

Returns the return value from the original function :

var callback = test.stub().returns(42);
var proxy = once(callback);

test.assert.strictEqual(proxy(), 42);

Mocks

Returns the return value from the original function :

var myAPI = {
  method: function() {}
};

var mock = test.mock(myAPI);

mock.expects('method').once().returns(42);

var proxy = once(myAPI.method);

test.assert.equal(proxy(), 42);

mock.verify();

Test should call a method with exceptions :

var myAPI = {
  method: function() {}
};

var mock = test.mock(myAPI);

mock.expects('method').once().throws();

test.exception(function() {
  myAPI.method();
})
  .isInstanceOf(Error);

mock.verify();

Matchers

Test should assert fuzzy :

var book = {
  pages: 42,
  author: 'cjno'
};

var spy = test.spy();

spy(book);

test.sinon.assert.calledWith(spy, test.sinon.match({
  author: 'cjno'
}));

test.sinon.assert.calledWith(spy, test.sinon.match.has('pages', 42));

Test should stub method differently based on argument types :

var callback = test.stub();

callback.withArgs(test.sinon.match.string).returns(true);
callback.withArgs(test.sinon.match.number).throws('TypeError');

test.bool(callback('abc')).isTrue(); // Returns true

test.exception(function() {
  callback(123); // Throws TypeError
})
  .isValid(function(err) {

    if (err.name === 'TypeError') {
      return true;
    }
  });

Combining matchers :

var stringOrNumber = test.sinon.match.string
  .or(test.sinon.match.number);

var bookWithPages = test.sinon.match.object
  .and(test.sinon.match.has('pages'));

var book = {
  pages: 42,
  author: 'cjno'
};

var spy = test.spy();
var otherSpy = test.spy();

spy(book);
otherSpy(10);

test.sinon.assert.calledWith(spy, bookWithPages);
test.sinon.assert.calledWith(otherSpy, stringOrNumber);

Custom matchers :


var equal10 = test.sinon.match(function(value) {
  return value === 10;
}, 'value is not equal to 10');

var spy = test.spy();
var otherSpy = test.spy();

spy(10);
otherSpy(42);

// ok because the argument value 10 is identical to 10 expected
test.sinon.assert.calledWith(spy, equal10);

test.exception(function() {

  // throws an exception because the argument value 42
  // is not identical to 10 expected
  test.sinon.assert.calledWith(otherSpy, equal10);
})
  .hasMessage(/value is not equal to 10/);

Sandbox

it('test using test.sinon.test sandbox', test.sinon.test(function() {

  var myAPI = {
    method: function() {}
  };

  var mockMyApi = this.mock(myAPI).expects('method').once().returns(42);

  var proxy = once(myAPI.method);

  test.number(proxy()).isIdenticalTo(42);

  mockMyApi.verify();
}));

Testing Ajax

after(function() {
  // When the test either fails or passes, restore the original
  // jQuery ajax function (Sinon.JS also provides tools to help
  // test frameworks automate clean-up like this)
  jQuery.ajax.restore();
});

it('makes a GET request for todo items', function() {
  test.stub(jQuery, 'ajax');

  getTodos(42, test.spy());

  test.assert(jQuery.ajax.calledWithMatch({
    url: '/todo/42/items'
  }));
});

Faking time


// original function
function throttle(callback) {
  var timer;

  return function() {

    clearTimeout(timer);

    var args = [].slice.call(arguments);

    timer = setTimeout(function() {
      callback.apply(this, args);
    }, 100);
  };
}

var clock;

// before load the fake timers
before(function() {
  clock = test.useFakeTimers();
});

// after the test, restore the timers
after(function() {
  clock.restore();
});

it('calls callback after 100ms', function() {
  var callback = test.spy();
  var throttled = throttle(callback);

  throttled();

  // faking 99ms
  clock.tick(99);

  test.assert(callback.notCalled);

  // faking 1ms
  clock.tick(1);

  test.assert(callback.calledOnce);

  // Also:
  test.assert.strictEqual(new Date().getTime(), 100);
});