Unit testing a click event with asynchronous content I'm writing unit tests for a web application that has quite a lot of UI interactions. I would like some feedback regarding on how to handle click events with asynchronous code. 

My goal here is to simluate users interacting with the page. That's why I'm triggering events rather than making function calls.

Here is my idea so far:

$('#foo').on('click', function(){

     var dfd = $.Deferred();

     simulateAsync().done(dfd.resolve).fail(dfd.reject);
     $(this).data({ promise: dfd.promise() });
});

function simulateAsync(){

    var dfd = $.Deferred();

    setTimout(function(){
        $('body').text('foo');
        dfd.resolve();
    }, 1000);

    return dfd.promise();
}


Testing the code:

test('foo text set on body after 1000ms when clicking #foo', function(assert){

    var $foo = $('#qunit-fixture').find('#foo'),
        done = assert.async();

    $foo.trigger('click');

    var completePromise = $foo.data('promise');

    completePromise.done(function(){
        assert.equal($('#bar').text(), 'bar');
        done();
    });
});


http://jsfiddle.net/0m4t4ndt/2/
Without the real code to be tested, I can only provide an example of how I've tested something similar with QUnit and Sinon.JS, though you don't even need the latter.

You can either use Sinon.JS to mock a response to the AJAX call or substitute your own manual mock for $.getJSON or $.ajax. I find the former preferable as it documents the AJAX call and tests the full front-end code, but it's beyond the scope of this question. You could argue that you're needlessly testing jQuery itself, and you'd be right. YMMV :)

Mocking JQuery

Let's assume this click event handler that uses the /load-foo AJAX call to acquire a JSON string. I've omitted the extra deferred for simplicity as your current test of the promise attached to the #foo element is fine.

$('#foo').on('click', function(){
    $.getJSON('/load-foo', {
        foo: 'bar'
    }).done(function (foo) {
        $('body').text(foo);
    });
});


Here's how you would test this handler.

test('foo text via AJAX set on body after clicking #foo', function(assert) {
    var $foo = $('#qunit-fixture').find('#foo'),
        getJSON = $.getJSON;

    $.getJSON = function (url, data) {
        assert.equal(url, '/load-foo');
        assert.equal(data.foo, 'bar');
        return new $.Deferred().resolve('"foobar"');
    }

    $foo.trigger('click');

    assert.equal($('body').text(), 'foobar');
    $.getJSON = getJSON;
});


For safety, I'd recommend saving and restoring $.getJSON in the module's setup and teardown functions. If you don't, any exception thrown in the test will leave the mock in place.

Mocking AJAX

While this does result in a lot of boilerplate sometimes, you can use Sinon's fake server to mock the AJAX responses. The beauty here is that it will match up the request to the response to ensure your AJAX calls (URL, data, type, etc.) are all correct and then supplies a canned response.

Here's the same test above using a fake server.

module('AJAX tests', {
    setup: function () {
        this.server = sinon.fakeServer.create();
    },
    teardown: function () {
        this.server.restore();
    }
});

test('foo text via AJAX set on body after clicking #foo', function(assert) {
    var $foo = $('#qunit-fixture').find('#foo');

    // setup the expected request with the canned response
    this.server.respondWith('/foo', 
        [ 200, { 'Content-Type': 'text/json' }, '"foobar"' ]
    );

    $foo.trigger('click');

    // handle queued requests immediately
    this.server.respond();

    assert.equal($('body').text(), 'foobar');
});


If you're mocking a lot of JSON AJAX requests, you can create a common fixture to make this a little saner. You can call this.mockAjax with a number for an HTTP response code, an object to convert it to JSON, or a string as the raw response.

var ajaxFixture = {
    setup: function () {
        this.server = sinon.fakeServer.create();
    },
    teardown: function () {
        this.server.restore();
    },
    mockAjax: function (url, response) {
        var type = { 'Content-Type': 'text/json' };
        if (typeof response === 'number') {
            this.server.respondWith(url, [ response, type, '' ]);
        }
        else if (typeof response === 'object') {
            this.server.respondWith(url, [ 200, type, JSON.stringify(response) ]);
        }
        else {
            this.server.respondWith(url, [ 200, type, response ]);
        }
    }
);


Then you simply use this as the module's fixture:

module('AJAX tests', ajaxFixture);

test(...);


Non-Mocking Solution

As you say in your comment, you want to execute the full AJAX call to your backend service.


  Note: This is not unit testing anymore but rather functional or integration testing. Unit testing involves testing each small piece (unit) in isolation of the others: the click handler. Adding in jQuery, XMLHTTPRequest, and your backend service will make isolating bugs more difficult. But it is useful for making sure all the pieces work together.
  
  I still recommend doing actual unit tests in addition to integration testing for quicker development as they typically run much faster and can be executed with every change to a source/test file from your IDE.


First, modifying your click handler invalidates any test you run, so we'll need to leave that in place, i.e., no simulated call or extra deferred. Your best bet is to poll for the call to complete with an interval and fail the test if it doesn't complete within X seconds.

test('foo text via AJAX set on body after clicking #foo', function(assert) {
    var $foo = $('#qunit-fixture').find('#foo'),
        $body = $('body'),
        done = assert.async(),
        timeout = Date.now() + 5000, // timeout after 5s
        timer;

    $foo.trigger('click');

    timer = window.setInterval(function () {
        if ($body.text() === 'foobar') {
            assert.equal($body.text(), 'foobar');
            window.clearInterval(timer);
            done();
        }
        else if (Date.now() &gt;= timeout) {
            assert.ok(false, 'AJAX timed out');
            window.clearInterval(timer);
            done();
        }
    }, 250);
});


Obviously, this won't work if you're using Sinon's fake timers, so disable them for this test if enabled globally. Also, you'll need to figure out if a slow AJAX call will interfere with later tests when it finally completes and puts "foobar" into the body. Maybe you want to put it into the fixture itself instead of polluting the global body.