Unit testing Ajax requests with Mocha

Tags:

Ajax requests can easily go wrong. You can’t guarantee the connection and the server always work correctly. They are also often used to send user input to the server and back, so it’s vital the data is handled correctly.

But testing them can be tricky. It’s asynchronous, and also a good unit test must be isolated, so how can we do that when the code talks to the server?

Let’s take a look at some examples to learn how to test Ajax requests with ease.

Setup

Before we begin, we need to set up the necessary tools and things.

  • Create a directory where all the necessary files will be placed
  • Install Mocha, Chai and Sinon using npm install mocha chai sinon

Test runner

To keep things simple, I’m going to run the tests directly in the browser. Don’t worry if you would prefer using a console based runner – the tests themselves will work exactly the same.

Below is the test runner file we will use. I’m going to call it testrunner.html. You can download it from here

<!DOCTYPE html>
<html>
  <head>
    <title>Mocha Tests</title>
    <link rel="stylesheet" href="node_modules/mocha/mocha.css">
  </head>
  <body>
    <div id="mocha"></div>
    <script src="node_modules/mocha/mocha.js"></script>
    <script src="node_modules/sinon/pkg/sinon-1.12.2.js"></script>
    <script src="node_modules/chai/chai.js"></script>
    <script>mocha.setup('bdd')</script>
    <script src="myapi.js"></script>
    <script src="test.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

Note the paths for mocha.css, mocha.js, sinon-1.12.2.js and chai.js. Since we installed them using npm, they are within the node_modules directory. For Sinon, you may need to adjust the filename to match the installed version.

Also note the myapi.js and test.js files. These are our example module and test case, which I’ll introduce next.

Example module

Below I’ve created a basic module which does some Ajax requests. I’ll use this to show you the techniques for testing Ajax code.

I’m calling this file myapi.js, you can grab the file here

var myapi = {
  get: function(callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://jsonplaceholder.typicode.com/posts/1', true);
 
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        if(xhr.status == 200) {
          callback(null, JSON.parse(xhr.responseText));
        }
        else {
          callback(xhr.status);
        }
      }
    };
 
    xhr.send();
  },
 
  post: function(data, callback) {
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'http://jsonplaceholder.typicode.com/posts', true);
 
    xhr.onreadystatechange = function() {
      if(xhr.readyState == 4) {
        callback();
      }
    };
 
    xhr.send(JSON.stringify(data));
  }
};

This should look familiar. We have two functions, one for fetching data, another for posting data. I’m using the JSONPlaceholder API, which is nice for quick testing.

Test case skeleton

Let’s create a quick skeleton where we can add tests as we go through each scenario. I’m calling this file test.js

chai.should();
 
describe('MyAPI', function() {
  //Tests etc. go here
});

Mocha uses describe to create a test case. Next, we will add some tests to it.

One additional thing here – chai.should() – which enables “should style” assertions. This means we can easily verify our test results – or in other words, create assertions – by using a syntax like someValue.should.equal(12345).

Testing a GET request

We’ll start by creating a test to verify the fetched data is parsed from JSON correctly. But since we don’t want to send HTTP requests from unit tests, what can we do about the XMLHttpRequest?

We included the Sinon library in our test runner. With Sinon, we can create *test-doubles* – objects and functions which we can use to replace others, and alter their behavior. Among other things, we can replace XMLHttpRequest with one that we control.

We need to update our test case skeleton a bit. Below you’ll see the updated version, which you can download here

chai.should();
 
describe('MyAPI', function() {
  beforeEach(function() {
    this.xhr = sinon.useFakeXMLHttpRequest();
 
    this.requests = [];
    this.xhr.onCreate = function(xhr) {
      this.requests.push(xhr);
    }.bind(this);
  });
 
  afterEach(function() {
    this.xhr.restore();
  });
 
  //Tests etc. go here
});

As their names might lead you to guess, beforeEach and afterEach let you run some code before and after each test. Before each test, we enable a fake XMLHttpRequest and put each created request into an array. By saving the values into this.xhr and this.requests, they are available to be used elsewhere within the test case.

After each test, we use this.xhr.restore() to restore the original XMLHttpRequest object back.

Now with that out of the way, we can actually write our test…

it('should parse fetched data as JSON', function(done) {
  var data = { foo: 'bar' };
  var dataJson = JSON.stringify(data);
 
  myapi.get(function(err, result) {
    result.should.deep.equal(data);
    done();
  });
 
  this.requests[0].respond(200, { 'Content-Type': 'text/json' }, dataJson);
});

First some data: An object and its JSON version. We define these to avoid using having to repeat the values within the test. Next, we call myapi.get. In its callback, we validate result with the test data. We also call done(), which tells Mocha the asynchronous test is complete. Notice done was a parameter on the test function.

Finally, we call this.requests[0].respond. Remember the beforeEach function creates a listener to put all XMLHttpRequests into this.requests. By calling myapi.get, we create a request, and access it here.

Normally XMLHttpRequests don’t have a respond function. respond is a function in the fake, which we can use to send a response to the request. In this case, we set the status code as 200 to indicate success. We also set a Content-Type header as text/json to show the data is JSON formatted. The last parameter is the response body, which we set to the dataJson variable we set up earlier.

The fake XMLHttpRequest pretends the JSON we gave it was sent by a web server. Then myapi.get sees it and calls the callback. Looking at the callback, notice we compare the result against the data variable:

myapi.get(function(err, result) {
  result.should.deep.equal(data);
  done();
});

If the response is handled correctly, it should parse the data it received into an object. Since we set up the data and dataJson variables to represent that earlier, we simply compare against that.

We can now run the tests in our browser of choice. Open the testrunner.html file, and you should see a message about the test passing.

Testing a POST request

The posted data needs to be encoded as JSON and sent in the request body. Let’s write a test for this.

it('should send given data as JSON body', function() {
  var data = { hello: 'world' };
  var dataJson = JSON.stringify(data);
 
  myapi.post(data, function() { });
 
  this.requests[0].requestBody.should.equal(dataJson);
});

Like before, we first define the test data. Then, we call myapi.post. We only need to verify the data is correctly converted into JSON, so we leave the callback empty.

The final line has an assertion to verify the behavior. Like in the previous test, we access the created XMLHttpRequest, but this time we only need to verify it contains the correct data. We do this by comparing the requestBody property with the test data we defined earlier.

Testing for failures

As the final example, let’s test a failing request. This important because network connections can have problems and the server can have problems, and we shouldn’t leave the user wondering “What happened?”.

The example module uses node-style callbacks – It should pass errors into the callback as the first parameter. We can test it like below:

it('should return error into callback', function(done) {
  myapi.get(function(err, result) {
    err.should.exist;
    done();
  });
 
  this.requests[0].respond(500);
});

This time we don’t need any data. We just call myapi.get, and verify an error parameter exists. The last line sends an erroneous response code, 500 Internal Server Error, to trigger the error handling code.

Conclusion and next steps

Ajax requests are important to test. If you verify they work correctly, the rest of your application can trust it will not get bad values from them. That can save you a lot of headaches down the line.

What if you’re using jQuery Ajax instead of plain XMLHttpRequest? No problem. You can use exact same methods shown here to test your code. You can use the same approach with Sinon’s fake XMLHttpRequests, as behind the scenes, jQuery also uses the basic XMLHttpRequest object.