Unit Testing with QUnit in jQuery: Asynchronous Testing

Sometimes, you need to be able to control the flow of a test. An obvious example is an Ajax request. Looking at the following code sample, you see an Ajax request that is part of the test. Inside the callback are two assertions, ok and equal.

test(“stops & starts”, function() {

expect(2);

var url = “/data/json/”;

$.getJSON(url, {name: “Testing”}, function(data) {

ok(data, “data is returned from the server”);

equal(data.name, “Testing”, “We’re Testing”);

});

});

Code snippet is from broken-ajax-test.html

Running the test generates the result shown in Figure 14-9.

This is an unexpected result.

Because the Ajax request is asynchronous, neither of the tests is run. You’re not going to catch many errors that way. Thankfully, QUnit provides a pair of methods to control this flow: start and stop. Rewriting the previous test to work as expected, the test is paused with stop() before the Ajax request, and then the test is restarted with start() at the end of the callback.

test(“asynch”, function() {

expect(2);

stop();

$.getJSON(“/data/json/”, function(data) {

ok( data, “We expect to get data from the server”);

equal(data.name, “Testing”, “We expect the name property to match);

start();

});

});

Code snippet is from start-stop.html

Running this code produces the desired result. As you see in Figure 14-10, both expected tests are run and both pass.

1. Using asyncTest

Like the convenience methods available with $.Ajax, QUnit has a convenience method for the common pattern of starting a test, and then stopping it for an asynchronous action.

The following code sample shows the previous example rewritten with that convenience method, asyncTest:

asyncTest(“asynch”, function() {

expect(2);

$.getJSON(“/data/json/”, function(data) {

ok( data, “We expect to get data from the server”);

equal(data.name, “Testing”, “We expect the name to match);

start();

});

});

Code snippet is from asynctest.html

This is a definitely a recommended option. It guarantees your asynchronous tests will be set up correctly and will allow you to differentiate between regular and asynchronous tests. Doing so will make your tests more readable. Other developers will know, without reading any of the assertions or other logic within the test body, that this is an asynchronous test.

2. Mocking Ajax Requests

Though it’s possible to test Ajax requests with live data, it’s not recommended. As you’ll remember from earlier in the chapter, one of the fundamentals of unit testing is that tests be self-contained. To that end, it’s a best practice to mock your Ajax requests.

Thankfully, there’s a plugin already available to do just that in QUnit. Mockjax from the fine folks at appendto (https://github.com/appendto/jquery-mockjax) allows you to set up URLs that will be captured by the Mockjax plugin. Doing so means they’ll be intercepted before they hit the traditional Ajax method and the response will be handled by Mockjax. Mockjax allows you to craft a testable response and customize it with headers of your own design.

Rewriting the previous example to better follow testing best practices, the following example shows a way to wire up a proper, testable Ajax request with Mockjax.

In this example, the Mockjax plugin takes three arguments: the url to be captured and mapped to Mockjax, the responseTime, used to simulate latency, and the responseText, which, in this case, will be converted to the JSON data passed into any successful Ajax callback methods.

With Mockjax, the test is now self-contained.

test(“asynch”, function() {

expect(2)

$.mockjax({

url:’/data/json/’,

responseTime: 100,

responseText: {

status:  ‘success’,

name:  ‘Testing’

}

});

stop();

$.getJSON(“/data/json/”, function(data) {

ok( data, “We expect to get data from the server”);

equal( data.name, “Testing”, “We expect the name match”);

start();

});

});

Code snippet is from mockjax.html

Even without an Internet connection or a completed data service, the test will pass. This allows you to test your code without worrying about the state of other parts of the system. If you’re working in a large team, this ability to write and test to an interface without the interface existing is invaluable. Figure 14-11 shows a completed test using mockjax.

3. Additional Mockjax Features

Because modern web application development needs to handle more than just a simple, successful JSON request, you’ll be glad to know that Mockjax has enough features to handle most Ajax situations. It’s worth exploring them in full because you never know what you might have to accommodate in testing, but the two that follow are worth looking at right off the bat.

3.1. Using a Mockjax Data Proxy

One especially nice feature is the ability to create a data proxy. Listing out a couple of data elements when setting up a simple test is convenient, but if you’re trying to test a full interface, it can be awkward to wrap a large data structure inside the plugin setup call. To fix this, Mockjax has an option to set a data proxy, which will point to a static file to serve as the data source for your Ajax request. All it takes, other than having the file itself, is to add an optional proxy parameter when the plugin is set up. The simplest possible example would look like this:

$.mockjax({

url:  ‘/data/json’,

proxy:  ‘/mock/data.json’

});

Do that and you can use complicated data structures without worrying about embedding them directly in your testing framework. Note that you do need to have access to JSON.stringify, which may require the use of json2.js (https://github.com/douglascrockford/JSON-js) if you’re going to be testing in legacy browsers.

3.2. Adding Additional HTTP Headers

Mockjax also allows you to set additional HTTP headers. For advanced Ajax programming, this is vital. Some standard ones, like HTTP status and contentType are available directly from the Mockjax setup object. Others can be set using an optional headers property, which can contain a series of name/value pairs representing the desired HTTP headers. The following example shows all three of these headers:

$.mockjax({

url:  ‘/data/json’,

//file not found!

status: 40,

contentType:  ‘text/json’,

headers: {

secretSauce:  ‘shhh’

}

});

Source: Otero Cesar, Rob Larsen (2012), Professional jQuery, John Wiley & Sons, Inc

Leave a Reply

Your email address will not be published. Required fields are marked *