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

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
more page dynamics, less client logic

Ajaxed pages frequently need to update many parts of the page within ajax callbacks. Here I'll outline how we use jQuery and Rails do this on Mavenlink. Our approach encodes behavior declaratively in markup and minimizes client-side logic.

PIVOTAL LABS
Standup 3/24/2009: Browser History with Javascript and Page Based Json

Interesting Things

  • Browser History with Javascript and Page Based Json: One of our projects solved the vexing problem of browser history for a page that has initial page provided json with subsequent ajax updates. A simple page back operation will display the originally downloaded data, not the updated data. The solution is to add a unique id for each page, and store these ids in a cookie. When an ajax request updates the page it removes its page id from the cookie. When you use the back button, each page checks to see if its unique id is in the cookie, and if it is not, it forces a reload.
    Really Simple History was mentioned as another way to manage javascript/ajax history.
  • Rubymine Build 784 has the Weirdest. Bug. Ever.: This may only be a problem if you work on a mac and you need to enter capital letters in rubymine dialogs like find and replace ;-). Many of us are fans of intellij/rubymine, but we wish they had a better test process. To be fair, rubymine is in public preview, so expect the occasional bug or two.

PIVOTAL LABS
Standup 01/16/2009: onReady() for AJAX, Web Sprites & Detecting UTF-8

Interesting Things

  • Web based sprite generator - here

This also makes the generated sprite really small which is great if you care about page load times. A Ruby+ImageMagick sprite generator might also be a good thing to build.

  • Cool way of detecting if a file is UTF-8 enconded using Ruby+IConv - here

Ask for Help

"Is there an onReady() for AJAX events?"

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.