Skip to content

cjohansen.no

Faking Timers and Dates with Sinon

In addition to low-level tools for stubbing and mocking and high-level tools to integrate with test runners, Sinon provides a few useful utilities. This post explains the first of these, which is fake timers and Dates.

Testing time

Client-side JavaScript frequently utilizes setTimeout, clearTimeout, setInterval and clearInterval to defer execution. When testing code that depends on these functions you have two choices:

Sinon offers a solution for the second choice, stubbing timers, which is inspired by JsUnit's JsUnitMockTimeout.js. The timers that ship with JsUnit provide a good start, but I find them a bit lacking:

So I wrote my own and included it in Sinon. Even if you don't want anything else from Sinon, you can use the timers described here - they're implemented completely independent from the rest of Sinon.

Faking timers with Sinon

Like JsUnit, Sinon provides a clock object which you can use to manually control the passage of time (sounds nice, doesn't it?) Here's an example of how you'd use Sinon to fake setTimeout and friends:

setUp: function () {
    this.clock = sinon.useFakeTimers();
},

tearDown: function () {
    this.clock.restore();
},

"test something using timers": function () {
    var stub = sinon.stub();
    setTimeout(stub, 1000);

    this.clock.tick(1000);

    assert(stub.called);
}

By calling sinon.useFakeTimers(), Sinon creates a new clock object and returns it. The function also overrides the global timer functions with clock.setTimeout and so on. The clock exposes a tick method which you can pass a number of milliseconds, and any timers due for execution within the passed time-frame are called. Finally, the clock.restore() call in tearDown resets the global functions.

Advantages of faking time

Using fake timers has one obvious advantage: fast tests. Rather than having the test runner sit idly around doing nothing for a bunch of milliseconds, we can instruct it to just carry out whatever it is it needs to do within the next x milliseconds, and be done with it in about a single millisecond.

Another less obvious, but very important, advantage of faking time is clarity. Nice and clear unit tests are easier to maintain, and work better as unit level documentation. Explicitly stating the number of milliseconds to wait makes the test better at communicating its intent. This is assuming async tests are implemented using wait and resume style methods. If you use a wait function that takes a number of milliseconds, the clock doesn't add much clarity.

Note that while you can achieve the same speed by using sinon.stub to simply stub e.g. setTimeout and friends, you cannot achieve the same kind of clarity this way.

Faking Dates

In addition to timers, we sometimes need to control the "now" in tests. If you have code which grabs the current time, counts elapsed time or similar using new Date() (or Date.now() in supporting browsers), it can sometimes be useful to control the time used by the Date constructor. Particularly if the date objects are used somehow in interaction with setTimeout and friends.

A practical example: For the book I implemented a comet client which used long polling. Long polling works by making a request which is held by the server until data is available. Upon completion, a new request is immediately made. To avoid DDOS-ing the server when data is frequently available, I added an option to set a minimal interval between requests. Because the duration of a request is unknown when polling like this, elapsed time for each request was tracked using new Date(). To write reliable and readable tests for this kind of code you have to control both the Date constructor and the timers.

The useFakeTimers function accepts a few optional arguments. If you don't pass it anything, it fakes out setTimeout, clearTimeout, setInterval and clearInterval and starts the clock at the current time. You can control what time the clock should start with by passing it a number: sinon.useFakeTimers(0); starts the clock with current time 0. You can also control what methods it should fake by providing them as string arguments. So to include the Date constructor, you'll do:

setUp: function () {
    this.clock = sinon.useFakeTimers(0, "setTimeout", "clearTimeout",
                                      "setInterval", "clearInterval", "Date");
},

tearDown: function () {
    this.clock.restore();
},

"test should create fake time": function () {
    var now = new Date(),

    assertEquals(0, now.getTime());
},

"test should pass time": function () {
    this.clock.tick(10);
    var now = new Date();

    assertEquals(10, now.getTime());
}

Looking at this example I'm thinking perhaps the default behavior should be to stub them all, and rather allow naming functions in those cases when you only need to fake some of them. What do you think?

Fake dates are real dates, too

Note that the only thing Sinon fakes with dates is how dates are created by calling Date with no arguments. If you pass arguments to the Date constructor, it works as usual. The date objects returned from the fake constructor are real dates, and the fake constructor mirrors all available Date properties. If the browser supports it, Sinon also fakes Date.now. That's it. Everything else is as real as can be.

Automating fake timers

In addition to the useFakeTimers method, I've introduced the sinon.sandbox object. This object inherits from sinon.collection object ( explained previously). In addition to automating collections of stubs and mocks, the sandbox also mirrors the useFakeTimers method. By calling it through the sandbox, the timers will be restored as well when you call e.g. sandbox.verifyAndRestore().

I'm planning to use sandbox in place of collection in sinon.test to allow better integration of all of Sinon's features into any given test framework. My idea is to have an object, say sinon.config, which controls how sinon.test automates the use of sinon.sandbox for every test. More on that later.

For now you can use the sandbox as such:

setUp: function () {
    this.sandbox = sinon.sandbox.create();
    this.sandbox.useFakeTimers();
},

tearDown: function () {
    this.sandbox.restore();
},

"test something": function () {
    this.sandbox.stub(jQuery, "ajax");
    this.sandbox.clock.tick(10);
    // etc
}

Next up is the fake XMLHttpRequest, which is almost there, but I need to add some way to fake data returning from the server. I have some ideas, cooked up together with @augustl which I'll get working on real soon.

As always, feedback, criticism, ideas, questions - very welcome!

Hook me up on Twitter: @cjno.

Possibly related

2006 - 2012 Christian Johansen Creative Commons License