Skip to content

cjohansen.no

Sinon.JS 1.1.0 out now

Boy, time passes by quickly. I started writing Sinon.JS while writing my book on TDD with JavaScript, and in just a month, Sinon.JS has celebrates its first birthday (0.5.0 was released June 9th 2010). Today I am happy to announce Sinon.JS 1.1.0, the first significant update since 1.0.0, released last December. In this post I'll take you through what's new and exciting in 1.1.0.

As always, Sinon.JS honors semantic versioning, meaning that if your code works with 1.0.x, 1.1.0 is a drop-in replacement. Eager? get your copy right away.

Bug fixes

Sinon.JS is just another project to show that properly testing your code pays off. Very few bugs have been filed, even with quite widespread use. I personally use Sinon.JS almost daily, and have stumbled upon very few problems. Still, a few minor issues have been sorted out, and Sinon.JS is now more bullet proof than ever.

Spies

Spies have gained one important new method: withArgs. This little gem allows you to create a spy that will only record calls for a specific set of arguments. This is mildly useful for spies, but as you will see, when stubs inherit this method it gets really interesting.

Here is how you would use it with spies:

"test should call method once with each argument": function () {
    var object = { method: function () {} };
    var spy = sinon.spy(object, "method");
    spy.withArgs(42);
    spy.withArgs(1);

    object.method(42);
    object.method(1);

    assert(spy.withArgs(42).calledOnce);
    assert(spy.withArgs(1).calledOnce);
}

If you call withArgs on the same spy with the same arguments multiple times, you always get the same spy, which allows for the expressive syntax seen in the example above. withArgs also returns a new spy that supports the full spy API.

Stubs

withArgs

Because stubs inherit the full spy API, they also gained the new withArgs method. This adds a sorely missed feature in Sinon.JS: the ability to cause a stub to behave differently in response to different input.

"test should stub method differently based on arguments": function () {
    var callback = sinon.stub();
    callback.withArgs(42).returns(1);
    callback.withArgs(1).throws("TypeError");

    callback(); // No return value, no exception
    callback(42); // Returns 1
    callback(1); // Throws TypeError
}

The arguments can be any value of course, not just numbers.

yields and yieldsTo

Callbacks are not exactly uncommon in JavaScript. Stubs have supported the callsArg and callsArgWith methods almost since the beginning to help with automatically invoking callbacks passed to stubs. With 1.1.0, there are two new methods that make this slightly prettier, and slightly less implementation specific.

The yields([arg1, arg2, ...]) method will invoke the first callback passed to the stub with the given arguments (if any). This means you no longer have to specify the argument index at which the callback can be found. It's a little bit of magic, but it is based on the fact that most callback based APIs accept a single callback somewhere among the arguments.

Here's an example of using the new method:

var todoList = {
    getItem: function (id, callback) {
        // ...
    }
};

"test should do something when the callback is called": function () {
    sinon.stub(todoList, "getItem").yields({ id: 1, text: "Something", isDone: false });

    todoList.getItem(1, function (item) {
        assertEquals(item, { id: 1, text: "Something", isDone: false });
    });
}

Obviously, testing that a stub behaves as configured is just silly, but you get the point.

A related method is yieldsTo. It works like yields, only it calls a callback received as a property on an object argument. You provide the property name, and Sinon.JS finds the right object:

"test should fake successful ajax request": function () {
    sinon.stub(jQuery, "ajax").yieldsTo("success", [1, 2, 3]);

    jQuery.ajax({
        success: function (data) {
            assertEquals([1, 2, 3], data);
        }
    });
}

Sandboxed stubs

When using the stub method through a sandbox (typically looks like this.stub(...)), you can now stub any kind of property, not just functions. This is useful if you want to override some value inside a single test and have it automatically restored afterwards.

Mocks

Mocks have received quite an overhaul. Previously, Sinon.JS would only consider mock expectations in the strict order they were defined. This has never been the exact intention, and was considered a bug. This fact was confirmed by people reporting to me that this behavior was unexpected. In any case, you can now define mock expectations as you wish, and when the mock is called, Sinon will figure out which expectation to consume next.

Additionally, mocks have significantly improved their error reporting. For instance, consider the following example from the docs:

"test mock failure messages": function () {
    var myAPI = { method: function () {} };

    var mock = sinon.mock(myAPI);
    mock.expects("method").withArgs(42).once().returns(true);
    mock.expects("method").twice().returns(2);

    myAPI.method();

    mock.verify();
}

With Sinon.JS 1.0.2 and below, this test would fail with "method received no arguments, expected 42". In 1.1.0, you'll get this:

Expected method(42[, ...]) once (never called)
Expected method([...]) twice (called once)

In other words, Sinon will print a full report of expected and received calls. This should entirely remove the need to whip out some logging statement or additional asserts to figure out why your tests are failing.

Assertions

Related to the mock improvements are the new and improved failure messages from assertions. As you know, testing is all about getting as much valuable feedback as possible, and Sinon has stepped it up one notch.

"test assertion failure messages": function () {
    var myAPI = { method: function () {} };

    sinon.stub(myAPI, "method").returns(true);

    myAPI.method();
    myAPI.method(42);
    myAPI.method("Hey", "There", "Fella");

    sinon.assert.calledOnce(myAPI.method);
}

Sinon.JS 1.0.2 and below will fail this test with "expected method to be called once but was called thrice". In 1.1.0 you get:

expected method to be called once but was called thrice
    method() => true
    method(42) => true
    method(Hey, There, Fella) => true

Sinon now also exposes the sinon.format(object) method which you can override if you want more intelligent formatting of arguments. The default implementation simply coerces objects to strings, but you could do fancy stuff like quote strings and so on. Sinon 1.2.0 will ship with awesome formatting.

The fake server

Auto responding - mockup development with Sinon.JS

Some work has been done on the fake server to make it suitable for mockup development. By calling server.autoRespond(10);, the server will automatically respond to requests after 10ms. This means you can drop in Sinon.JS and a server.js file on an HTML page to work with a fake server, defined in JavaScript.

Response functions

The respondWith method accepts a wide variety of arguments, as it has done. What's new in 1.1.0 is that it will now accept three types of responses: a string, which defines the body of the response (old), an array: [200, { "Headers": "here" }, "Body"] (old) and a function.

When using a function to respond to the request, it will receive the XMLHttpRequest object. You can also specify URL and so on, but the function has the final word. The function is only called when method and URL (if any) match, and then it can decide to respond or not (by calling respond on the xhr object):

server.respondWith("GET", "/todo-items", function (xhr) {
    xhr.respond(200, { "Content-Type": "application/json" }, '[]');
});

Additionally, you can add capture groups to URL regular expressions, and the function will receive whatever is caught:

server.respondWith(/\/todo-items\/(\d+)/, function (xhr, id) {
    xhr.respond(200, { "Content-Type": "application/json" }, '[{ "id": ' + id + ' }]');
});

The website

The website also got a small facelift. I once again changed the docs, now they're back on one really long page. I'm kinda struggling to find the proper format for the documentation, so please don't hesitate to get in touch if you have problems understanding Sinon.JS, or you have ideas on how to improve the docs.

Finally, thanks a lot to everyone who uses Sinon.JS, everyone who contributed patches, reported bugs and generally showed interest. Keep 'em coming.

Now, get Sinon.JS 1.1.0 and let me know what you think!

Possibly related

2006 - 2014 Christian Johansen Creative Commons License