Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Taming JavaScript in practice: AJAX

Commonly the JavaScript side of AJAX ends up untested, tightly coupled to the server-side code, and difficult to read. In a previous post, we saw how testability led to the ability to refactor our code to make it readable. This time we’ll focus on ways to test AJAX (which is a fairly lengthy topic in itself) and take it as read that once our code is tested we’ll be in a good position to refactor towards readability and to generally tame the complexities of client-side AJAX code.

Example

Suppose we have a very simple calculator application: the user enters a number into a textfield and presses a button labeled “Double”. The application sends the number to the server, and the server sends back a response containing twice the value of the number. The doubled value is then displayed in a second textfield. Of course, this is an artificial example, but the structure of the code is common enough to demonstrate practices for testing AJAX. Let’s use raw XmlHttpRequests here; I’ll post again separately with how to adapt this if you’re using prototype.js. Our code might look like this:

<script language="javascript">
var request;
function calculate() {
  var enteredValue = document.entryfield.value;
  if (window.XMLHttpRequest)
    request = new XMLHttpRequest();
  else
    request = new ActiveXObject("Microsoft.XMLHTTP");
  request.onreadystatechange = callback;
  request.open("GET", "/calculate?value=" + enteredValue, true);
  request.send(null);
}

function callback() {
  if (request.readyState == 4) {
    if (request.status != 200) {
      document.outputfield.value = "Error: " + request.status;
      return;
    }
    var responseValue = request.responseText;
    document.outputfield.value = responseValue;
  }
}

</script>

<input type="text" name="entryfield">
<input type="button" onclick="calculate()" value="Double">
<input type="text" name="outputfield">

When the user presses the “Double” button, an AJAX request is sent to a servlet, passing the value the user entered in the “entryField” text field. When the server responds, the text in the response is displayed in the “outputField” text field. If the server didn’t respond successfully, we display an error message in “outputField” containing the error code.

Starting a Test Page

Let’s try to write a Test Page for our code:

<script language="javascript" src="/path/to/calculate.js"></script>
<script language="javascript">
function testClickCalculate() {
  document.entryfield.value = "5";
  calculate(); //but wait - we don't have a server
  //now what?
}
</script>

<input type="text" name="entryfield">
<input type="text" name="outputfield">
</body>

Hmm – how do we proceed? As things are, when we run our test, our request will get sent off to a server that isn’t running. We don’t have enough control over our environment to continue with the test.

So, how can we test AJAX?

The most important thing to bear in mind is that we are trying to write unit tests for our JavaScript – tests that exercise just a unit of our JavaScript code at a time. So we certainly don’t want to bring a server into the picture – that’s way out of scope for our JavaScript unit tests. Instead, what we want to do is insulate ourselves from the machinery of the request/response server interaction. The typical point at which we set up our insulation is at the level of the XmlHttpRequest: rather than a real request, we will use a mock version in our test – a pretend version of the request that we control.

Testing the request

Our first task, then, is to set things up so that our test uses a mock request and our code uses a real one:

function calculate() {
  ...
  request = createRequest();
  ...
}

function createRequest() {
  if (window.XMLHttpRequest)
    return new XMLHttpRequest();
  else
    return new ActiveXObject("Microsoft.XMLHTTP");
}

but in our Test Page, we implement createRequest differently. JsUnit comes with a library called jsUnitAjax.js, which contains a mock implementation of XmlHttpRequest.

<script language="javascript" src="/path/to/jsunit/lib/jsUnitAjax.js"></script>

function createRequest() {
  return new MockXmlHttpRequest();
}

Our Test Page’s implementation overrides the real implementation of createRequest. Good: now we are able to call calculate without worrying about a real request getting sent to the server. Let’s go back and continue with our test.

function testClickCalculate() {
  document.entryfield.value = "5";
  calculate();
  assertEquals("GET", request.method);
  assertEquals("/servlet?value=5", request.url);
  assertTrue(request.isAsync);
  assertTrue(request.sendCalled);
  assertNull(request.data);
  assertEquals(callback, request.onreadystatechange);
}

Notice that we’re testing that calling calculate() sends the request, and how the request gets set up – its method, its URL, etc – by examining the mock request. We also verify that the correct callback method has been set up for when the server responds. We aren’t using a real server, and we don’t care (in this test) about a response.

Testing the response

So, that’s half the story. How about testing the response? We need to simulate the server responding to the request. We’ve already tested that the correct callback is set up; let’s take advantage of that now.

function testValidServerResponse() {
  request = new MockXmlHttpRequest();
  request.readyState = 1;
  callback();
  assertEquals("", document.outputfield.value);

  request.readyState = 2;
  callback();
  assertEquals("", document.outputfield.value);

  request.readyState = 3;
  callback();
  assertEquals("", document.outputfield.value);

  request.readyState = 4;
  request.status = 200;
  request.responseText = "50";
  callback();
  assertEquals("50", document.outputfield.value);
}

We go through each readyState, ensuring that nothing happens until state 4. For state 4, we give the mock request a status of 200 and a responseText of “50”, and then verify that calling callback() populates the outputfield correctly. Notice how we’ve tested just the response logic, without using a real server and without the need to set up a meaningful request.

Simulating a server-side error

We have one more test – we need to test what happens when the server responds unsuccessfully:

function testInvalidServerResponse() {
  request = new MockXmlHttpRequest();
  request.readyState = 4;
  request.status = 500;
  callback();
  assertEquals("Error: 500", document.outputfield.value);
}

This time we give the mock request a status of 500, and we verify that the output field contains the expected error message.

Comments
  1. Frank Manno says:

    Just came across this article, and love how you explain the mock request/response concepts!

    If we wanted to test actual mock objects from our response (ie: a JSON object), how much different would the setup be?

  2. Edward Hieatt says:

    You can make the mock response contain anything you like. So, if your real response is JSON, just make it be the JSON (as a string).

  3. Daya says:

    Hi Edward

    In your blog you mentioned jsUnit comes with a library called jsUnitAjax.js , but that is not the case, atleast not here https://sourceforge.net/project/showfiles.php?groupid=28041&packageid=19823&release_id=404277

    Is MockXmlHttpRequest proprietary to Pivotal?

    Could u please point me to the right source.?

    thanks
    -daya

  4. Edward Hieatt says:

    Sorry, I should have mentioned that it’s not in the released version on SourceForge.net – it’s in the latest tagged version in SVN. We’re planning on releasing it soon (as part of the JsUnit 2.2 release).

  5. Daya says:

    Could you please email me the library jsUnitAjax.js at daya dot sharma at gmail dot com

    thanks

  6. Dan says:

    Wow, Incredible Work – Thanks!

    Here’s the code to inject the mock into ExtJS, which is my weapon of choice:

    Ext.lib.Ajax.createXhrObject = function(transactionId) {
    return{conn:mockRequest, tId:transactionId};
    }

    Dan

    Ps. ^ I do wish these idiots wouldn’t post this rubbish in your comment system.

  7. Edward Hieatt says:

    Daya – check out HEAD from SourceForge, or see here:

    http://jsunit.svn.sourceforge.net/viewvc/jsunit/trunk/jsunit/lib/

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *