Testing the application: object interactions and AJAX calls

August 29, 2014

The final step in the series of posts about adding tests for the Backbone application is to add tests for functions that interact with other objects as well as dealing with those pesky AJAX calls. This addresses the “spies and mocks will be addressed later” can that has been kicked down the rode so far in this series.

As always, these tests (as well as the full context of the API and application) are available.

Adding Jasmine Ajax support

To handle testing functions that involve AJAX requests, the jasmine-ajax plugin is added to the test require configuration and the test.js file is updated to require it as a dependency.

Stubbing out XMLHttpRequest

The jasmine-ajax plugin adds the ability to override the global XMLHttpRequest for the page. To activate (and deactivate) the plugin, the beforeEach and afterEach functions need to be modified.

beforeEach(function() {
  jasmine.Ajax.install();
});

afterEach(function() {
  jasmine.Ajax.uninstall();
});

Testing AJAX calls

Using the AJAX interception provided by jasmine-ajax, it is possible to add expectations about a request URL and body.

it("should build the correct search model", function() {
  model.search(function() {});
  var expectedRequestData = {
    policyNumber: "PL123",
    firstName: "John",
    lastName: "Doe"
  };
  request = jasmine.Ajax.requests.mostRecent();
  expect(request.url).toBe(SERVICE_API + "policies/search");
  expect(request.data()).toEqual(expectedRequestData);
});

Success and error responses can be specified for the responses to a request. By controlling the response to the intercepted request, the application code can be manipulated into different paths.

In the cases below, the tests pass in an error function that is used to capture the error messages returned by the workflow code when an unexpected response or an error response is returned.

it("should return an error message when no results found", function() {
  var message;
  model.search(function(error) {
    message = error;
  });
  request = jasmine.Ajax.requests.mostRecent();
  jasmine.Ajax.requests.mostRecent().response({
    "status": 200,
    "contentType": "text/plain",
    "responseText": "[]"
  });
  
  expect(message).toEqual("No policies found with the search criteria.");
});

it("should return the specified error message when the error response text exists", function() {
  var message;
  model.search(function(error) {
    message = error;
  });
  request = jasmine.Ajax.requests.mostRecent();
  jasmine.Ajax.requests.mostRecent().response({
    "status": 400,
    "contentType": "text/plain",
    "responseText": "Error"
  });
  
  expect(message).toEqual("Error");
});

Using spies to test object interactions

When the application model tests were initially added, the tests for functions that interact with the workflow were skipped. Be introducing spies, the tests can verify that the model did interact with the workflow without needing to actually worry about that the workflow would do.

it("should call the next step in the workflow when one result found", function() {
  model.search(function() {});
  spyOn(model.workflow, "next");
  request = jasmine.Ajax.requests.mostRecent();
  jasmine.Ajax.requests.mostRecent().response({
    "status": 200,
    "contentType": "text/plain",
    "responseText": "[{}]"
  });
  
  expect(model.workflow.next.calls.count()).toBe(1);
});

These spies are used for any of the tests in which dealing with the state of external collaborators is more work than it is worth.

Everything Can Now Be Tested

By adding in spies and AJAX mocks, testing all of the application code is within reach.