Agile Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 637 posts at DZone. You can read more from them at their website. View Full User Profile

Unit testing JavaScript code when Ajax gets in the way

07.14.2011
| 6813 views |
  • submit to reddit

The previous part of this article is here, but it's not required for understanding the current topic.

Unit testing is one of the best tools I have in my box to aid the design of my code. Yet I'm not always in the environment where unit testing is easy: JavaScript frameworks are maturing, and isolation of tests presents the same issues of other languages: global state and external interactions (plus asynchronous behavior).

One of the obvious external interactions is Ajax calls, performed by whatever library you happen to be using. This article is the story of how we got to unit test JavaScript client code by stubbing Ajax calls; although then we moved to a end-to-end testing approach for other reason, at least this know-how will be kept here and won't be lost.

Mechanics

JavaScript is a kind of open language, in the Ruby open classes sense. You can substitute almost any method exposed by a public Api:

jQuery.val = function() { ... };

This ability means that if we know which library calls the code under test is using, we can test it even if it was not designed with Dependency Injection like we'll prefer in PHP or Java. Since parts of the code may not even be under our control, this is often the only choice.

This form of isolation is obtained by tampering with global state, so it may be dangerous. The teardown phases become fundamental, at least until the testing frameworks get better at sandboxing the tests.

Stubbing the library calls

A first choice for testing code that performs Ajax calls in it is to stub the library method it's using. If you are in the 0.01% of people who instantiates XMLHttpRequest objects directly, it's easy to wrap this creation into a method you can stub out later.

For jQuery, the target is the jQuery.ajax method. In the case of Ext JS, it is the Ext.Ajax.request. However, in the latter case Ext.Ajax.request is called with so many different options by other Ext JS components that is going to be difficult to write a general purpose stub.

The mechanics are the following:

  • save the old method in a variable (var oldAjax = jQuery.ajax;)
  • Substitute it with a closure that saves the callback in a variable. This closure may check its arguments (becoming a kind of mock).
  • Use the callback to pass back a canned response, written in literal JSON, XML or whatever you return from the server side.
  • Restore the old method (teardown phase) with a new assignment.

Stubbing XMLHttpRequest

An alternative way to stub Ajax calls is to stub out the creation of XMLHttpRequest. Since XMLHttpRequest is just a global object of the class Function, it can be substituted. Moreover, you don't have to do it by hand: Sinon is a JavaScript library that does exactly what we need. It works also in Explorer since checks also for ActiveXObject; it provides an higher level Api, so that you don't have to rewrite the whole object and all its methods.

Sinon provides various testing utility methods which let you use more than one workflow: a list of correspondences between URLs and responses, or single expectations. It's just a .js file, so it will work with whatever testing framework you're using:

var fakeXhr = sinon.useFakeXMLHttpRequest();
var requests = [];

fakeXhr.onCreate = function (xhr) {
    requests.push(xhr);
};

jQuery.ajax({ url: "/my/page" });
// this should go in an always executed tearDown method
fakeXhr.restore();

assertEquals("/my/page", this.requests[0].url);

When multiple Ajax calls have to be mocked, the Api changes a little:

var server = sinon.fakeServer.create();
server.respondWith("responseText content"); // also configurable by URL

var called = false;
callback = function(text) {
    assertEquals("responseText content", text);
    called = true;
    
};
jQuery.ajax({ url: "/my/page", success: callback });
server.respond();
// should go in the tearDown phase
server.restore();

assertTrue(called);

The server.respond() call eliminates asychronicity from the test: after that, you're sure that the Ajax handler functions have been called. You can proceed in making assertions or continuing the test.

Ext JS currently requires a larger step, since it checks via polling if a request is complete instead of relying on callbacks (don't ask me why): setInterval should be stubbed as well.

var oldSetInterval = setInterval;
callbacks = [];
setInterval = function (callback, delay) {
    callbacks.push(callback);
};
// stub and call code under test as before...
setInterval = oldSetInterval;
for (i in callbacks) {
    callbacks[i].call();
}
// assertions...

This time, callbacks[i].call() avoids the asynchronicity: the Ext handlers, which should check the response text and code are called immediately, after the Ajax stubbing has been performed.
Playing with setTimeout, setInterval and the other time-related functions may be dangerous if the testing framework does not support it; it may be calling them for internal purposes, like avoiding an infinite test. The issue was preminent in jsTestDriver, but was solved in 2009: now jsTestDriver makes its own copies of setInterval and other sensitive functions before starting to run the test cases. If you're using jsTestDriver, you're free to play with these global functions.

Conclusions

Testing JavaScript code is always possible, and when there is more logic involved than an Ajax call, stubbing helps isolating the system under test. Testing with real Ajax calls won't even be possible in my favourite environment, jsTestDriver, as the code is served from a webserver on localhost and not from the real application. Indeed the solutions describes in this article are appropriate for unit testing, not end-to-end or functional one.

Published at DZone with permission of Giorgio Sironi, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Elijah Manor replied on Fri, 2011/07/15 - 11:27am

Yeah, mocking ajax calls is a very handy thing to do. The dynamic & functional nature of JavaScript make it very powerful and enjoyable for me.

appendTo has developed 2 different libraries where you can also handle the mocking feature you describe above.

If you want to keep using jQuery.ajax, jQuery.get, etc... you can use the $.mockJAX plugin. All you do is keep your jQuery code as is and just add some $.mockJAX statements defining what mock data to return when it matches certain URLs.

http://code.appendto.com/plugins/jquery-mockjax

You can also use amplify.request & amplify.request.define to handle setting up requests to your server and mocking a result or changing it's definition down the road.

http://amplifyjs.com/api/request/

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.