Testing Backbone collections

Backbone collections are basically Arrays with superpowers:

  • They come with a bunch of enumerable functions built in, such as map, sort, and select
  • They have support for all sorts of events such as addition, removal, and even changes made to individual models it contains
  • They support reading data from a remote server

Expect to use it a lot in conjunction with models in your Backbone application.

Let's see a small code snippet to get a feeling on how it works. Here we instantiate a new collection passing an array with an initial data item for it to start:

var collection = new Backbone.Collection([
  { id: 1, name 'first' }
]);

It will by default, create a new Backbone.Model for each object in that array, but it is possible to specify your own custom Model objects.

Next, we show how to add a new Model to the collection:

collection.add(new Backbone.Model({ id: 2, name: 'second' }));

And finally, we can retrieve the added model by its id attribute:

var model = collection.get(1)
model.get('name') // first

They also come with event support, so it is possible to listen for changes on the collection:

collection.on('add', function (newModel) { })

And a lot more features.

Declaring a new collection

Before we can dig some of the features and how we can test Backbone collections, we must first learn how to declare one, and for that we are going to need an example.

We are going to create a new collection of Stocks, it will need a new source and spec file, written at src/StockCollection.js and spec/StockCollectionSpec.js respectively.

In the spec file, we can start by expecting that this new StockCollection to be a Backbone collection and also that it is a collection of Stocks:

describe("StockCollection", function() {
  var collection;

  beforeEach(function() {
    collection = new StockCollection();
  });

  it("should be a Backbone Collection", function() {
    expect(collection).toEqual(jasmine.any(Backbone.Collection));
  });

  it("should be of Stocks", function() {
    expect(collection.model).toBe(Stock);
  });
});

To define which model a collection contains is not a requirement, but by specifying, the collection knows how to create new instances of the model, for example, while doing a fetch.

Here is the StockCollection implementation:

(function (Backbone, Stock) {
  var StockCollection = Backbone.Collection.extend({
    model: Stock
  });

  this.StockCollection = StockCollection;
})(Backbone, Stock);

Make the StockCollection an extension of the base Backbone.Collection, and set its model as the Stock.

Sync and AJAX requests

As with the model, it is also possible to fetch data of a collection, except that we are retrieving one or more models in a single request.

In StockCollection, we want it to have a fetch function that updates its models. To make it more interesting, we are going to add a requirement that we must send to the server about which stock data we want, based on the stocks available in the collection.

Our server will be expecting a URL containing a query string with the stock IDs, something like (for our development server): http://0.0.0.0:8000/stocks/?ids[]=AOUE&ids[]=COUY.

This spec is going to be a little more complicated, so we are going to start with just its skeleton, so you can have a feeling of how we are going to write it:

describe("StockCollection", function() {
  describe("given a populated collection", function() {
    describe("when fetch", function() {
      it("should request by the Stocks it contains", function(){});
      it("should update its models share price", function(){});
    });
  });
});

And then, we are going to show you how each piece is implemented, starting with given a populated collection:

describe("given a populated collection", function() {
  beforeEach(function() {
    model1 = new Stock({ symbol: 'AOUE' });
    model2 = new Stock({ symbol: 'COUY' });

    collection = new StockCollection([
      model1,
      model2
    ]);
  });
});

Where it creates a collection with a set of two models.

Next, we need to perform the fetch function. For this to work, we are going to use the Sinon.JS Fake server:

describe("when fetch", function() {
  beforeEach(function() {
    fakeServer = sinon.fakeServer.create();
    fakeServer.respondWith(JSON.stringify([
      {
        symbol: 'AOUE',
        sharePrice: 20.13
      },
      {
        symbol: 'COUY',
        sharePrice: 14
      }
    ]));
    
    collection.fetch();
    fakeServer.respond();
  });

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

You can see that here we are configuring the Fake server with a Fake response, performing the fetch function, and telling the Fake server to respond to all the requests made.

Finally, we can check if our collection is making the correct request:

it("should have request by the Stocks it contains", function() {
  // encoded '/stocks?ids[]=AOUE&ids[]=COUY'
  var url = '/stocks?' + $.param({ ids: ['AOUE', 'COUY'] });

  expect(fakeServer.requests[0].url).toEqual(url);
});

And then check if the models were updated with the Fake response data:

it("should update its models share price", function() {
  expect(model1.get('sharePrice')).toEqual(20.13);
  expect(model2.get('sharePrice')).toEqual(14);
});

And that is it. Although extensive, it is pretty simple to understand. But what about the actual implementation? For switching back to the source file we need to define a URL property that is used by Backbone while making requests:

(function (Backbone, Stock) {
  var StockCollection = Backbone.Collection.extend({
    model: Stock,
    url: function () {
      return "/stocks" + idsQueryString.call(this);
    }
  });

  function modelIds () {
    return this.map(function (model) { return model.id; });
  }

  function idsQueryString () {
    var ids = modelIds.call(this);

    if (ids.length === 0) { return ''; }
    return '?' + $.param({ ids: ids });
  }

  this.StockCollection = StockCollection;
})(Backbone, Stock);

There it is. You can see by the highlighted part that the URL is dynamic, and we are constructing it based on the IDs of the models stored on the collection, much in the way that we wanted.

Back to the explanation on how to test the Sync of models, we told you how the spec was outdated. This could also be the case in here. See how you could simplify this spec while still guaranteeing that it works.

There are many more features to Backbone collections, for more information, be sure to check the official documentation available at http://backbonejs.org/#Collection.

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

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