Author avatar

Chris Parker

How to Write Unit Tests for Ajax Requests using Mocha

Chris Parker

  • Apr 28, 2020
  • 10 Min read
  • 804 Views
  • Apr 28, 2020
  • 10 Min read
  • 804 Views
Web Development
React

Introduction

Test-driven development (TDD) involves writing tests before actual coding takes place. A common test stack accompanied with TDD includes Mocha, Chai, and Sinon.

Briefly, Mocha is a JavaScript test framework running on Node.js and in the browser. It enables users to easily perform asynchronous testing. Chai is a BDD/TDD assertion library that also works on Node, and a browser can be easily paired with any JavaScript testing framework. Sinon provides standalone test spies, stubs, and mocks for JavaScript and works well alongside any unit testing framework.

It is only natural to assume that Ajax requests do not always go as planned, because you can neither ensure the stability of the connection to nor validate the functionality of the server itself. Yet Ajax requests usually carry user data back and forth to the server. The integrity of such data is crucial, and you must handle it meticulously. However, testing these requests is a critical and sensitive issue. It is an asynchronous process; thus, your ideal unit test must be isolated. But how can you isolate it when your code interacts with the server?

This guide covers how to test Ajax requests efficiently and effectively.

Setup

Before you dive in, prepare the environment and set up the required tools for testing.

Start by creating a directory to house the necessary files. Next, use npm install mocha chai sinon to install Mocha, Chai, and Sinon.

Test Runner

You can run these tests straight in the browser. They perform similarly in a console-based runner, so do not be afraid to use one if you prefer.

Check the test runner below. You may download it here under the name testrunner.html.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
  <head>
    <title>Mocha Test</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>
javascript

Because you installed mocha.css, mocha.js, sinon-1.12.2.js, and chai.js using npm, they now reside within the node_modules directory. If your installed version doesn't match the one in this guide, you might need to modify the filename for Sinon to match your installed version.

In this guide, myapi.js and test.js files are the example module and test case.

Example Module

This elementary module generates the usual Ajax requests. Use it to understand the mechanisms of testing an Ajax code.

The code calls the file myapi.js, you can download it here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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));
  }
};
javascript

Here you have two functions: the first function fetches data while the other posts data. Use JSONPlaceholder API for swift, painless testing.

Test Case Skeleton

To add tests as you continue with the following scenarios, create a skeleton file. The filename in this guide is test.js.

1
2
3
4
5
chai.should();
 
describe('My API', function() {
  //All the tests would go here
});
javascript

To generate a test case, Mocha uses describe. The next step is adding tests to the file above.

Keep in mind that chai.should() allows you to include should style assertions. In other words, you can verify your test results by using a syntax similar to someValue.should.equal(12345).

Testing a GET Request

In the following scenario, you will create a test to verify correct JSON parsing of the fetched data. Since you cannot send HTTP requests from unit tests, here is what you can do about the XMLHttpRequest.

Start by including the Sinon library in your test runner. Sinon enables you to create test-doubles, which are objects and functions that can replace others and modify their behavior simultaneously. In the following, you will replace XMLHttpRequest with a double that you control.

Update the test case skeleton as follows, or download the modified version here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
});
javascript

As the names imply, you can use beforeEach and afterEach to run a designated piece of code before and after each test.

Prior to the test, create a fake XMLHttpRequest and arrange these requests in an array. Thus, the values that are saved in this.xhr and this.requests are ready for you to use anywhere in the test case.

When the test finishes, you need to restore the XMLHttpRequest object's original state. You can use this.xhr.restore() to do so easily.

After that, simply write the test:

1
2
3
4
5
6
7
8
9
10
11
it('should parse the fetched response 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);
});
javascript

As you see, in order to avoid repeating the values within the test, you need to define your data--an object and its JSON version, to be precise. The next step is to call myapi.get, which validates your result with the test data. Subsequently, use done() so that Mocha knows that the asynchronous test is over. Note how done was used as a parameter for the test function.

For the final step, use this.requests[0].respond. Keep in mind that using beforeEach generates a listener that will place all your XMLHttpRequests in this.requests, while creating the requests happens using myapi.get.

XMLHttpRequests don’t usually include a respond function. A respond function is used in the fake XMLHttpRequests to send a response to the received request. Set the status code to 200 to indicate that the operation was successful, and set the Content-Type header as text/json to indicate that the data format is JSON. Finally, set the final parameter, which is the response body to the dataJson variable you defined earlier.

Now, your fake XMLHttpRequest will pretend that a web server sent the JSON that you provided. When myapi.get checks it, it summons the callback within which the result and the data variable are compared:

1
2
3
4
myapi.get(function(err, result) {
  result.should.deep.equal(data);
  done();
});
javascript

Handling the response includes pairing the received data within an object. Providing that you already prepared the data and dataJson variables above, you only need to compare the result against them.

To run these tests in a browser of your choice, simply open the testrunner.html file, where you will find a message regarding test passing.

Testing a POST Request

To test the POST request, you need to encode the posted data as JSON and send it within the request body:

1
2
3
4
5
6
7
8
it('should post the given response 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);
});
javascript

Similar to the previous case, you need to set up data prior to the test and call myapi.post. Since you only have to ensure that your data was successfully parsed as JSON, you can leave the callback empty. Now you need to verify the behavior of the request using an assertion. Similar to the test earlier, you have to access the XMLHttpRequest you created and make sure its requestBody property is equal to the test data you set up earlier.

Testing for Failures

The final scenario in this guide is testing a failing request. These kinds of tests are important since network connections may drop or the server might experience issues. In these cases, the user knows what happened.

In this case, you can use node-style callbacks. This way, errors are passed as the first parameter in the callback. You can do it as follows:

1
2
3
4
5
6
7
8
it('should return an error into the callback', function(done) {
  myapi.get(function(err, result) {
    err.should.exist;
    done();
  });
 
  this.requests[0].respond(500);
});
javascript

Note that you do not have data to test. You only need to call myapi.get to check whether an error parameter is present. You can trigger the error handling code by sending the error response code: 500 Internal Server Error.

Conclusion

It is essential to test Ajax requests so that other aspects in your app trust the values that these requests provide. This is a lifesaver as the app grows and becomes more complicated.

You can employ the same methods above to test your code even if you are using jQuery Ajax, as jQuery uses the same basic XMLHttpRequest object as Sinon’s fake XMLHttpRequests.

5