Testing asynchronous javascript

19 Nov 2010

setTimeout and unit tests

We all want to test our code... but unfortunately Javascript is not always the easiest to tame. That's a shame, because with its loosely typed nature it can really benefit from good test coverage. However there's quite a few hurdles we have to jump over, from setting up the DOM to mocking the right dependencies...

One of them that had me scratching my head lately was testing asynchronous code. I don't mean HTTP requests that we usually manage to mock quite well, but these sneaky setTimeout. Personally, I try to stay away from delayed execution. However sometimes you want to use a 3rd party plugin like livequery or simpleModal which doesn't share the same concerns... and as much value as they provide, some of our code becomes quite hard to test.

A few solutions

One simple approach to handling these cases is to overwrite setTimeout for the duration of the test with something like:

setTimeout = function(callback, time) { callback(); }

However that's quite different from the real behaviour, since this will execute the callback straight away. There is a chance the code under test relies on the delay, since even a real setTimeout(0) would yield the execution before executing the code. This subtle difference makes our first approach fail in some cases, including jquerylive.

Fortunately, other people have thought about this problem, and written a great solution to this: jsUnitMockTimeout. Packaged as part of jsUnit but useable on its own, it gives you the ability to simulate the passing of time. Internally, it replaces most of the timeout-related functions and stores callbacks in a queue to know when they were supposed to be executed. Clever!

function testThatMethodThatUsesSetTimeout() {
    Clock.reset();
    var result = [];
    addOneNumberPerSecond(result);
    Clock.tick(3000);
    assertThat(message, equalTo([1, 2, 3]));
}

Note that this test does not take 3 seconds to run... only a few milliseconds tops!

Comments