6 min read

(For more resources related to this topic, see here.)

Testing Backbone applications is no different than testing any other application; you are still going to drive your code from your specs, except that Backbone is already leveraging a lot of functionality for you, for free. So expect to write less code, and consequently less specs.

Testing Backbone Views

We already have seen some of the advantages of using the View pattern in Testing Frontend Code, and are already creating our interface components in such a manner. So how can a Backbone View be different from what we have done so far?

It retains a lot of the patterns that we have discussed as best practices for creating maintainable browser code, but with some syntax sugar and automation to make our life easier.

They are the glue code between the HTML and the model, and the Backbone View’s main responsibility is to add behavior to the interface, while keeping it in sync with a model or collection.

As we will see, Backbone’s biggest triumph is how it makes an easy-to-handle DOM event delegation, a task usually done with jQuery.

Declaring a new View

Declaring a new View is going to be a matter of extending the base Backbone.View object.

To demonstrate how it works we need an example. We are going to create a new View and its responsibility is going to be to render a single investment on the screen.

We are going to create it in such a way that allows its use by the InvestmentListView component.

This is a new component and spec, written in src/InvestmentView.js and spec/InvestmentViewSpec.js respectively.

In the spec file, we can write something similar to this:

describe("InvestmentView", function() {
var view;
beforeEach(function() {
view = new InvestmentView();
});
it("should be a Backbone View", function() {
expect(view).toEqual(jasmine.any(Backbone.View));
});

});

Which translates into an implementation that extends the base Backbone View component:

(function (Backbone) {
var InvestmentView = Backbone.View.extend()
this.InvestmentView = InvestmentView;
})(Backbone);

And now we are ready to explore some of the new functionalities provided by Backbone.

The el property

Like the View pattern a Backbone View also has an attribute containing the reference to its DOM element.

The difference here is that Backbone comes with it by default, providing:

  • view.el: The DOM element
  • view.$el: The jQuery object for that element
  • view.$: A scoped jQuery lookup function (the same way we have implemented)

And if you don’t provide an element on the constructor, it creates an element for you automatically. Of course the element it creates is not attached to the document, and is up to the View’s user code to attach it.

Here is a common pattern you see while using Views:

  1. Instantiate it:

    var view = new InvestmentView();

  2. Call the render function to draw the View’s components

    view.render()

  3. Append its element to the page document:

    $('body').append(view.el);

Given our clean implementation of the InvestmentView, if you would go ahead and execute the preceding code on a clean page, you would get the following result:

<body>
<div></div>
</body>

An empty div element; that is the default element created by Backbone. But we can change that with a few configuration parameters on the InvestmentView declaration.

Let’s say we want the DOM element of InvestmentView to be a list item (li) with an investment CSS class. We could write this spec using the familiar Jasmine jQuery matchers:

describe("InvestmentView", function() {
var view;
beforeEach(function() {
view = new InvestmentView();
});
it("should be a list item with 'investment' class", function() {
expect(view.$el).toBe('li.investment');
});
});

You can see that we didn’t use the setFixtures function, since we can run this test against the element instance available on the View.

Now to the implementation; all we have to do, is define two simple attributes in the View definition, and Backbone will use them to create the View’s element:

var InvestmentView = Backbone.View.extend({
className: 'investment',
tagName: 'li'
}
);

By looking at the implementation you might be wondering if we shouldn’t test it. Here I would recommend against it, since you wouldn’t get any benefit from that approach, as this spec is much more solid.

That is great, but how do we add content to that DOM element? That is up to the render function we are going to see next.

var view = new InvestmentView({ el: $('body') });

But by letting the View handle its rendering, we get better componentization and we can also gain on performance.

Rendering

Now that we understand that it is a good idea to have an empty element available on the View, we must get into the details of how to draw on this empty canvas.

Backbone Views already come with an available render function, but it is a dummy implementation, so it is up to you to define how it works.

Going back to the InvestmentView example, let’s add a new acceptance criterion to describe how it should be rendered. We are going to start by expecting that it renders the return of investment as a percentage value. Here is the spec implementation:

describe("InvestmentView", function() {
var view, investment;
beforeEach(function() {
investment = new Investment();
view = new InvestmentView({ model: investment });
});
describe("when rendering", function() {
beforeEach(function() {
investment.set('roi', 0.1);
view.render();
});
it("should render the return of investment", function() {
expect(view.$el).toContainHtml('10%');
});
});

});

That is a very standard spec with concepts that we have seen before and the implementation is just a matter of defining the render function on the InvestmentView declaration:

var InvestmentView = Backbone.View.extend({
className: 'investment',
tagName: 'li',
render: function () {
this.$el.html('<p>'+ formatedRoi.call(this) +'<p>');
return this;

}
});
function formatedRoi () {
return (this.model.get('roi') * 100) + '%';
}

It is using the this.$el property to add some HTML content to the View’s element. There are some details that are important for you to notice regarding the render function implementation:

  • We are using the jQuery.html function, so that we can invoke the render function multiple times without duplicating the View’s content.
  • The render function returns the View instance once it has completed rendering. This is a common pattern to allow chained calls, such as:

    $('body').append(new InvestmentView().render().el);

Now back to the test. You can see that we weren’t testing for the specific HTML snippet, but rather, that just 10 percent text was rendered. You could have done a more thoroughly written spec by checking the exact same HTML at the expectation, but that ends up adding test complexity with little benefit.

Summary

In this article, you have seen how to use Backbone to do some heavy lifting, allowing you to focus more on your application code. I showed you the power of events, and how they make integration between different components much easier, allowing you to keep your models and Views in sync.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here