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() >= 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.
simulateAsync
? \$\endgroup\$simluateAsync
with an actual ajax call. My goal here is just to know when all the code in my click handler has executed, including the promise(s). \$\endgroup\$