DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. JavaScript
  4. Unit testing JavaScript code when Ajax gets in the way

Unit testing JavaScript code when Ajax gets in the way

Giorgio Sironi user avatar by
Giorgio Sironi
·
Jul. 14, 11 · Interview
Like (0)
Save
Tweet
Share
15.57K Views

Join the DZone community and get the full member experience.

Join For Free

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.

unit test JavaScript library

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 5 Software Developer Competencies: How To Recognize a Good Programmer
  • Java REST API Frameworks
  • Reliability Is Slowing You Down
  • Container Security: Don't Let Your Guard Down

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: