Harry's Engineering

Harry's
Engineering
Blog

13.03.14

Async Testing With Jasmine and MooTools

By: Andy O'Neill

Unit testing asynchronous code can be tricky, but for Javascript this is a problem that needs to be solved on day one. As heavy RSpec users we took to Jasmine like ducks to water but it took us a while before we had an elegant way to test asynchronous code like AJAX requests.

At first we were doing pretty clunky things like this to detect if an event had been triggered:

// how not to do it...
it("adds new cart items", function () {
    // manually listen for events
    var numItemsAdded = 0;
    Harrys.cart.addEvents({
        item_added: function () {
            numItemsAdded += 1;
        }
    });

    // trigger events
    _.each(testItems, function (value, key, list) {
        Harrys.cart.addItem(value);
    });

    // expect correct number of events
    expect(numItemsAdded).toEqual(4);
});

There are a couple of problems with this approach: it isn’t actually asynchronous; and it becomes very tedious to test the properties of the events. To fix the asynchronous part, we needed to learn the async features of Jasmine (runs() and waitsFor().) To mock and spy on Ajax requests, we threw in the excellent mock-ajax.js library.

I was also inspired by Luiz Ribeiro to come up with a better way. His technique is jQuery-specific, whereas we use MooTools events. In the end we have something that can do this:

it("can submit to queued", function () {
    // Setup listening for the queued event
    spyOnEvent(order, 'queued');

    // Trigger a (mock) Ajax request
    runs(function() {
        jasmine.Ajax.stubRequest('/some/endpoint').andReturn({
            "status": 200,
            "contentType": 'application/json',
            "responseText": '{"status" : "queued", "job_id" : "foo"}'
        });
        order.submit();
    });

    // Expect the event to have been thrown within 500ms
    waitsForEvent(order, 'queued', 500);

    // Expect our code to have done the right thing with the event
    runs(function() {
        expect(jasmine.Ajax.requests.mostRecent().url).toEqual("/some/endpoint");
        expect(order.status).toEqual('queued');
        // inspect the data from the event
        var data = lastEventData(order, 'queued');
        expect(data.job_id).toEqual("someId");
    });
});

Download

Get the code and more extensive documentation from GitHub

Direct link: event-spies.js

Gotchas

One little gotcha we came across: once you call runs(), all further code needs to be in a runs or waitsFor block, otherwise it will be executed immediately. Even code that comes after waitsForEvent needs to be in a runs block.