Sinon.JS

Sinon.JS is a great library created by Christian Johansen, author of the great book, Test-Driven JavaScript Development, to make easy dealing with Stubs, Spies, and Mocks.

Although Jasmine already has support for Stubs and Spies, we are going to use a specific functionality of Sinon.JS to test AJAX requests, its FakeXMLHttpRequest and FakeServer functions.

The main difference between a Stub and a Fake, as you will see with the FakeXMLHttpRequest object, is that a Fake is like a simpler but still complete implementation of a real component, and it is usually set at a system level.

Installing Sinon.JS

Before we dig into the spec implementation, first we need to add Sinon.JS to the project. Go to http://sinonjs.org/ and download the current release, placing it inside the lib folder.

We also need to add it to the SpecRunner.html file, so go ahead and add another script:

<script type="text/javascript" src="lib/sinon.js"></script>

A Fake XMLHttpRequest

Whenever you are making AJAX requests with jQuery, under the hood it is using the XMLHttpRequest to actually perform the request.

XMLHttpRequest is the standard JavaScript HTTP API. Even though its name suggests that it uses XML, it supports other types of content such as JSON, the name has remained the same for compatibility reasons.

So instead of Stubbing jQuery, we could instead Fake, the global XMLHttpRequest object. That is exactly what Sinon.JS does with its FakeXMLHttpRequest implementation.

Let's rewrite the previous spec to use this Fake implementation:

describe("when fetched", function() {
  var xhr;

  beforeEach(function() {
    var fetchRequest;
    xhr = sinon.useFakeXMLHttpRequest();

    xhr.onCreate = function (request) {
      fetchRequest = request;
    };

    stock.fetch();

    fetchRequest.respond(
      200,
      { "Content-Type": "application/json" },
      '{ "sharePrice": 20.13 }'
    );
  });

  afterEach(function() {
    xhr.restore();
  });

  it("should update its share price", function() {
    expect(stock.sharePrice).toEqual(20.13);
  });
});

First, we tell Sinon.JS to replace the original implementation by its Fake using the sinon.useFakeXMLHttpRequest function.

Then, we add an observer to get the newly created requests by setting a function as a value of the xhr.onCreate attribute, storing them on a variable named fetchRequest.

We then invoke the stock.fetch function, which will invoke $.getJSON, creating a new XMLHttpRequest under the hood.

And finally, we use the fetchRequest variable (which contains the FakeXMLHttpRequest object caught by the observer), to respond with a fake content.

We use the respond function, which accepts three parameters:

  • An integer defining the HTTP status code
  • An object containing the HTTP headers
  • A string with the response body

Then, it's all a matter of running the expectations:

it("should update its share price", function() {
  expect(stock.sharePrice).toEqual(20.13);
});

Since Sinon.JS changes the global XMLHttpRequest object, you must remember to tell Sinon.JS to restore it to its original implementation after the test runs, otherwise you could interfere with the code (such as the Jasmine jQuery fixtures module) from other specs:

afterEach(function() {
  xhr.restore();
});

A Fake server

Sinon.JS's FakeXMLHttpRequest is a very good solution to stub AJAX requests, but things can start to get complicated if you need to deal with more than one request, or need to have different responses for different requests.

To help manage FakeXMLHttpRequest instances, Sinon.JS comes with another solution, the Fake server.

Sinon.JS Fake server abstracts the manipulation of the individual FakeXMLHttpRequest instances into a high-level API, that lets you focus on what response you want for a particular request type.

Again, let's rewrite the same example, but now using the Fake server functionality:

describe("when fetched", function() {
  var xhr;

  beforeEach(function() {
    xhr = sinon.fakeServer.create();
    xhr.respondWith([
      200,
      { "Content-Type": "application/json" },
      '{ "sharePrice": 20.13 }'
    ]);

    stock.fetch();

    xhr.respond();
  });
  afterEach(function() {
    xhr.restore();
  });

  it("should update its share price", function() {
    expect(stock.sharePrice).toEqual(20.13);
  });
});

Now, instead of dealing with XMLHttpRequest, we create a new instance of the Fake server using the sinon.fakeServer.create function.

Then, we call the respondWith function to configure the Fake server to always respond to requests with a Fake response.

After the stock.fetch() call, we tell the Fake server to respond to all made requests.

After each spec runs, it is also important to restore the original XMLHttpRequest behavior.

The coolest thing about Fake server is its ability to create different responses, based on different URLs. For instance, we could have written the previous server response as:

xhr.respondWith(
  '/stocks/AOUE',
  [
    200,
    { "Content-Type": "application/json" },
    '{ "sharePrice": 20.13 }'
  ]
);

Notice the extra parameter '/stocks/AOUE' telling the Fake server to only respond to requests made with that URL. It is even possible to specify the HTTP method (GET, POST, and so on) and to use regular expressions to match the URL:

xhr.respondWith(
  'GET',
  //stocks/(.+)/,
  [
    200,
    { "Content-Type": "application/json" },
    '{ "sharePrice": 20.13 }'
  ]
);

You can also pass a function to the body parameter, and have dynamic responses:

xhr.respondWith(
  'GET',
  //stocks/(.+)/,
  function (request, stockSymbol) {
    request.respond(
      200,
      { "Content-Type": "application/json" },
      '{ "sharePrice": 20.13 }'
    );
  }
);

Notice the stockSymbol parameter, it contains the matched value extracted from the request URL based on the //stocks/(.+)/ regular expression. Whenever using regular expressions and function bodies to handle requests on a Fake server, the matched strings passed to the function in the order they are found.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset