Sinon.JS

Standalone test spies, stubs and mocks for JavaScript.
No dependencies, works with any unit testing framework.

sinon-qunit

Seamless Sinon.JS integration for QUnit.

This page contains light introduction to the topics Sinon.JS implements as well as some examples, all in light of the sinon-qunit plugin. It is currently very basic, but will hopefully improve in the not-so-distant future. See the full API reference for a complete reference of Sinon.JS features.

Loading Sinon.JS

To set up a test case using QUnit and Sinon.JS, copy the below HTML template:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>QUnit/Sinon.JS</title>
    <link rel="stylesheet" href="qunit.css" type="text/css" media="screen">
  </head>
  <body>
    <h1 id="qunit-header">QUnit/Sinon.JS Test</h1>
    <h2 id="qunit-banner"></h2>
    <h2 id="qunit-userAgent"></h2>
    <ol id="qunit-tests"></ol>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="qunit.js"></script>
    <script type="text/javascript" src="sinon-1.12.1.js"></script>
    <script type="text/javascript" src="sinon-ie-1.12.1.js"></script>
    <script type="text/javascript" src="sinon-qunit-1.0.0.js"></script>
    <!-- Source and test files go here -->
  </body>
</html>

Note! You don't have to use sinon-qunit to use Sinon.JS with QUnit, but this is the recommended approach.

Test spies API reference

A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. A test spy can be an anonymous function or it can wrap an existing function.

Test spies are useful to test both callbacks and how certain functions/methods are used throughout the system under test.

Anonymous spies: this.spy()

Calling this.spy with no arguments creates an anonymous spy. The following test is taken from Morgan Roderick's PubSubJS and shows how anonymous spies are useful for testing callbacks.

test("should call all subscribers for a message exactly once", function () {
    var message = getUniqueString();

    var spy1 = this.spy();
    PubSub.subscribe(message, spy1);

    var spy2 = this.spy();
    PubSub.subscribe(message, spy2);

    PubSub.publishSync(message, "my payload");

    ok(spy1.calledOnce, "first subscriber called once");
    ok(spy2.calledOnce, "second subscriber called once");
});

Spying on existing methods

this.spy can also spy on existing functions. When doing so, the original function will behave just as normal (including when used as a constructor) but you will have access to data about all calls. The following is a slightly contrived example:

test("should inspect jQuery.getJSON's
      usage of jQuery.ajax", function () {
    this.spy(jQuery, "ajax");
    jQuery.getJSON("/some/resource");

    ok(jQuery.ajax.calledOnce);
    equals(jQuery.ajax.getCall(0).args[0].url, "/some/resource");
    equals(jQuery.ajax.getCall(0).args[0].dataType, "json");
});

With sinon-qunit, the jQuery.ajax method will be automatically restored/"unspied" after the test.

Spy interface

Spies provide a rich interface to inspect their usage. The above examples showed the calledOnce boolean property as well as the getCall method and the returned object's args property. There are three ways of inspecting call data.

The preferred approach is to use the spy's calledWith method (and friends) because it keeps your test from being too specific about which call did what and so on. It will return true if the spy was ever called with the provided arguments.

test("should call subscribers with message as first argument", function () {
    var message = getUniqueString();
    var spy = this.spy();

    PubSub.subscribe(message, spy);
    PubSub.publishSync(message, "some payload");

    ok(spy.calledWith(message));
});

If you want to be specific, you can directly check the first argument of the first call. There are two ways of achieving this:

test("should call subscribers with message as first argument", function () {
    var message = getUniqueString();
    var spy = this.spy();

    PubSub.subscribe(message, spy);
    PubSub.publishSync(message, "some payload");

    equals(spy.args[0][0], message);
});
test("should call subscribers with message as first argument", function () {
    var message = getUniqueString();
    var spy = this.spy();

    PubSub.subscribe(message, spy);
    PubSub.publishSync(message, "some payload");

    equals(spy.getCall(0).args[0], message);
});

The first example uses the two-dimensional args array directly on the spy, while the second example fetches the first call object and then accesses it's args array. Which one to use is a matter of preference, but the recommended approach is going with spy.calledWith(arg1, arg2, ...) unless there's a need to make the tests highly specific.

Test stubs API reference

Test stubs are functions (spies) with pre-programmed behavior. They support the full test spy interface in addition to methods which can be used to alter the stub's behavior.

As spies, stubs can be either anonymous, or wrap existing functions. When wrapping an existing function with a stub, the original function is not called.

The following example is yet another test from Morgan Roderick's PubSubJS which shows how to create an anonymous stub that throws an exception when called.

test("should call all subscribers, even if there are exceptions", function () {
    var message = getUniqueString();
    var error = getUniqueString();
    var stub = this.stub().throws();
    var spy1 = this.spy();
    var spy2 = this.spy();

    PubSub.subscribe(message, stub);
    PubSub.subscribe(message, spy1);
    PubSub.subscribe(message, spy2);

    PubSub.publishSync(message, undefined);

    ok(spy1.called);
    ok(spy2.called);
    ok(stub.calledBefore(spy1));
});

Note how the stub also implements the spy interface. The test verifies that all callbacks where called, and also that the exception throwing stub was called before one of the other callbacks.

Mocks API reference

Mocks are slightly different from spies and stubs in that you mock an object, and then set an expectation on one or more of its methods. Expectations implement both the spies and stubs interfaces and additionally set pass criteria upfront.

To see how mocks look like in Sinon.JS, here's one of the PubSubJS tests again, this time using a method as callback and using mocks to verify its behavior:

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

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

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

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

Fake timers API reference

Fake timers is a synchronous implementation of setTimeout and friends that Sinon.JS can overwrite the global functions with to allow you to more easily test code using them. Fake timers provide a clock object to pass time, which can also be used to control Date objects created through either new Date(); or Date.now(); (if supported by the browser).

When faking timers with IE you also need sinon-ie-1.12.1, which should be loaded after sinon-1.12.1.js.

The fake timers can be used completely stand-alone by downloading sinon-timers-1.12.1. When using the fake timers in IE you also need sinon-timers-ie-1.12.1. Load it after the first file.

test("should animate element over 500ms", function () {
    var el = jQuery("<div></div>");
    el.appendTo(document.body);

    el.animate({ height: "200px", width: "200px" });
    this.clock.tick(510);

    equals("200px", el.css("height"));
    equals("200px", el.css("width"));
});

Fake XMLHttpRequest API reference

Provides a fake implementation of XMLHttpRequest and provides several interfaces for manipulating objects created by it. Also fakes the native XMLHttpRequest and ActiveXObject (if available, and only for XMLHTTP progids). Helps with testing requests made with XHR.

When faking XHR with IE you also need sinon-ie-1.12.1, which should be loaded after sinon-1.12.1.js.

The fake server and XHR can be used completely stand-alone by downloading sinon-server-1.12.1. When using the fake server in IE you also need sinon-ie-1.12.1. Load it after the first file.

test("should fetch comments from server", function () {
    var xhr = this.sandbox.useFakeXMLHttpRequest();
    var requests = this.requests = [];

    xhr.onCreate = function (request) {
        requests.push(request);
    };

    var callback = this.spy();
    myLib.getCommentsFor("/some/article", callback);
    equals(1, this.requests.length);

    requests[0].respond(200, { "Content-Type": "application/json" },
                             '[{ id: 12, comment: "Hey there" }]');
    ok(callback.calledWith([{ id: 12, comment: "Hey there" }]));
});

Fake server

High-level API to manipulate FakeXMLHttpRequest instances.

test("should fetch comments from server", function () {
    var server = this.sandbox.useFakeServer();

    server.respondWith("GET", "/some/article/comments.json",
                       [200, { "Content-Type": "application/json" },
                        '[{ id: 12, comment: "Hey there" }]']);

    var callback = this.spy();
    myLib.getCommentsFor("/some/article", callback);
    server.respond();

    ok(callback.calledWith([{ id: 12, comment: "Hey there" }]));
});

Assertions API reference

Sinon.JS ships with a set of assertions that mirror most behavior verification methods and properties on spies and stubs. The advantage of using the assertions is that failed expectations on stubs and spies can be expressed directly as assertion failures with detailed and helpful error messages.

The assertions can be used with either spies or stubs.

test("should call subscribers with message as first argument", function () {
    var message = getUniqueString();
    var spy = this.spy();

    PubSub.subscribe(message, spy);
    PubSub.publishSync(message, "some payload");

    sinon.assert.calledOnce(spy);
    sinon.assert.calledWith(spy, message);
});

Sinon uses Semantic versioning.

Copyright 2010 - 2014, Christian Johansen. Released under the BSD license.