Testing Backbone.js Application

Exclusive offer: get 50% off this eBook here
Jasmine JavaScript Testing

Jasmine JavaScript Testing — Save 50%

Leverage the power of unit testing to create bigger and better JavaScript applications with this book and ebook

$19.99    $10.00
by Paulo Ragonha | September 2013 | Open Source

This article by Paulo Ragonha, author of Jasmine JavaScript Testing, provides how you can write tests to a Backbone.js application. You will learn about what to test, and not to test, when using Backbone.js.

(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:


Jasmine JavaScript Testing Leverage the power of unit testing to create bigger and better JavaScript applications with this book and ebook
Published: August 2013
eBook Price: $19.99
Book Price: $33.99
See more
Select your format and quantity:

About the Author :


Paulo Ragonha

Paulo Ragonha is a software engineer. He loves web development for the opportunities that it carries; "to be able to craft a piece of software that can be instantly accessible by anyone" (with internet connection, of course).

In his early days of software development, he was mostly involved in game development and Java. But since his discovery of Ruby and JavaScript, he has worked uniquely on web applications.

His last three projects were big JavaScript applications, developed entirely driven by tests and with amazing tooling support.

He has an amazing wife that he loves very much, lives in the beautiful Florianópolis, a coast city in the south of Brazil. He is a casual speaker, a biker, a runner, and a hobbyist photographer (he has earned an actual award taking pictures).

Books From Packt


Node Cookbook
Node Cookbook

Moodle JavaScript Cookbook
Moodle JavaScript Cookbook

JavaScript Unit Testing
JavaScript Unit Testing

Ext JS 4 Web Application Development Cookbook
Ext JS 4 Web Application Development Cookbook

Ext JS 4 First Look
Ext JS 4 First Look

Object-Oriented JavaScript
Object-Oriented JavaScript

JavaScript Testing Beginner's Guide
JavaScript Testing Beginner's Guide

Learning JavaScriptMVC
Learning JavaScriptMVC


No votes yet

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
7
E
z
C
n
4
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software