Appendix A. Getting Started with Jasmine

In Chapter 20, Testing with Jasmine, we discussed successful strategies for testing Backbone applications with Jasmine [31]. Here we present a brief introduction to Jasmine itself, with an eye toward testing Backbone code.

Jasmine tests are written in Javascript and look something like:

describe("appointments", function() {
  it("populates the calendar with appointments", function() {
    expect($('#' + fifteenth)).toHaveText(/Get Funky/);
  });
});

In this test, we have a bunch of appointments that have been attached to our calendar application. One of them indicates an appointment to get on the funk on the fifteenth of the month [32]. If this test were passing, is might look something like:

Successful funk.

As can been seen, Jasmine tests are evaluated in a browser [33]. Looking at those Jasmine results, we can get a decent idea of what Jasmine means when it claims to facilitate BDD. Specifically, the output of the tests almost reads like a specification.

From top to bottom, we are talking about a Calendar Backbone application. In that calendar application, we expect that a collection of appointments will populate the calendar with UI representations of themselves.

We are getting ahead of ourselves of course. Since we want to be counted among the cool kids and/or hipsters of the programming world, we want to drive the implementation of this feature via our test. Without an appointment view class, our test fails:

Failure to funk.

It fails because our Appointment view does not actually do anything:

var Appointment = Backbone.View.extend({});

It is a Backbone view, so it will respond to render() calls—but it will not actually render anything. It has an el property that can be inserted into the DOM, but it is an empty <div>. So, of course, our test fails.

If this were a book on BDD, we might take you through the steps of demonstrating simple tests that get something displayed. Then, we could write a second test that verifies that information is coming from our collection rather than being hard-coded to allow the first test to pass. But, since this is a Backbone book, let’s skip ahead to what is necessary to make this test pass with data from the collection:

var Appointment = Backbone.View.extend({
  template: _.template(
    '<span class="appointment" title="{{ description }}">' +
    '  <span class="title">{{title}}</span>' +
    '  <span class="delete">X</span>' +
    '</span>'
  ),
  render: function() {
    this.$el.html(this.template(this.model.toJSON()));
    return this;
  }
});

Our collection view has ultimate responsibility for inserting this individual appointment View into the DOM at the correct location. All we have to do is ensure that the view will include the model’s title.

Done and done thanks to our view class. Now we have legitimately achieved our passing test:

Successful funk.
[Important]

Even if you are not adhering to the principals of BDD, you should always ensure that removing code makes tests fail. If you remove code and the test still passes, chances are you are not testing what you think you are testing. Actually, strike that, if you remove code and your tests still pass, then your test is worthless.

When first getting started with a Backbone / Jasmine test suite, the quick and dirty thing to do is "Jasmine Standalone". This involves pointing our browser at a single web page on the local filesystem. For instance, if we are building our application in $HOME/repos/calendar, then we would want to load our tests at file:///home/dafunk/repos/calendar/spec/SpecRunner.html.

The contents of the spec runner looks something like:

<head>
  <!-- Jasmine files -->
  ...

  <!-- Library files -->
  ...

  <!-- Test helpers -->
  ...

  <!-- Include spec files ... -->
  ...

  <!-- Include source files ... -->
  ...

  <!-- Initialize Jasmine env -->
  <script type="text/javascript">
    (function() {
      // ...
    })();
  </script>
</head>

Since this test is file-based, all of those sections need to include references to other files on the file system. For example, the jasmine files would be linked in as:

<!-- Jasmine files -->
<script type="text/javascript"
  src="lib/jasmine-1.1.0/jasmine.js"></script>
<script type="text/javascript"
  src="lib/jasmine-1.1.0/jasmine-html.js"></script>

The specs themselves would be linked in as:

<!-- include spec files here... -->
<script type="text/javascript"
  src="CalendarSpec.js"></script>

<!-- include source files here... -->
<script type="text/javascript"
  src="../public/javascripts/Calendar.js"></script>

The specs and testing libraries should generally be kept in a separate directory from the actual code. Here, we have the Jasmine test libraries and the actual test file in the same directory while the application code being tested is in a separate public top-level directory (see below for a more detailed breakdown of how these files might be organized).

As for the spec itself, it should look something like:

describe("Calendar", function() {
  describe("the page title", function() {
    it("contains the current month", function() {
      /* Code verifying the title */
    });
  });
});

The first, outermost spec describes the highest level concept being tested—here the calendar Backbone application. Inside that, we describe the specific aspect of the application being tested—in this case, the title of the page. Finally, we have one or more blocks that enumerate the expected behavior of the code.

An it() block uses Jasmine "matchers" to describe the expected behavior. To describe the expectation that the title should contain the ISO 8601 date, we can write:

  describe("the page title", function() {
    it("contains the current month", function() {
      expect($('h1')).toHaveText(/2011-11/);
    });
  });

At first, this will fail with an error along the lines of:

A failing jasmine test.

But we can make our spec pass by implementing a Backbone title view:

var TitleView = Backbone.View.extend({
  tagName: 'span',
  initialize: function(options) {
    options.collection.on('calendar:change:date', this.render, this);

    $('span.year-and-month', 'h1').
      replaceWith(this.el);
  },
  render: function() {
    this.$el.html(' (' + this.collection.getDate() + ') ');
  }
});

With that, we have our passing test:

Successful funk.

There are definitely times that standalone Jasmine tests are not sufficient, which is what the next section discusses.

If your application grows, it will soon become too large for standalone Jasmine. The standalone approach lacks the capability to run under a continuous integration server. Standalone also make network requests very difficult. This is where the jasmine ruby gem steps into the picture.

[Tip]

The jasmine server is implemented in Ruby, so you will need that installed on your system. The best resource for this is the "Downloads" link on http://ruby-lang.org.

You will also need the rubygems library. Despite being universal in the Ruby community, the rubygems library is not bundled with Ruby. You can find instructions for installing rubygems at: http://docs.rubygems.org/read/chapter/3

With ruby and rubygems installed, you are ready to install the jasmine server. Per the jasmine server instructions, installation is accomplished via two commands:

$ gem install jasmine
$ jasmine init

At this point the server can be run as:

$ rake jasmine

There is a fair bit of configuration required in the Jasmine server. After installation of the server (and assuming that we already have the Backbone application started in Calendar.js), our directory structure might include the following:

...
├── public
│   └── javascripts
│       ├── backbone.js
│       ├── Calendar.js
│       ├── jquery.min.js
│       ├── jquery-ui.min.js
│       └── underscore.js
├── spec
│   └── javascripts
│       ├── helpers
│       │   ├── jasmine-jquery.js
│       │   ├── sinon.js
│       │   └── SpecHelper.js
│       ├── CalendarSpec.js
│       └── support
│           ├── jasmine_config.rb
│           ├── jasmine_runner.rb
│           └── jasmine.yml
...

Libraries used by the actual application (our Backbone app and various supporting libraries) are stored under public/javascripts. Libraries only used for testing are stored along with the rest of the testing material under the spec/javascripts/ directory.

Configuration is done almost exclusively in the jasmine.yml configuration file. For the most part, the defaults are sound. The exception for Backbone applications is the src_files directive. By default, this loads javascript source files (the things under public/javascripts for us) in alphabetical order. This is disastrous for a Backbone application because it means that underscore.js would be loaded after backbone.js (ditto jquery.js).

To work around this, we need to explicitly layout our library files in the same order as they would be in the web page. Something along the lines of:

src_files:
    - public/javascripts/jquery.min.js
    - public/javascripts/underscore.js
    - public/javascripts/backbone.js
    - public/javascripts/**/*.js

The last line instructs Jasmine to slurp up everything (Jasmine is smart enough to ignore anything it has already loaded).

With that, we can run our test suite—either locally or under a continuous integration server—by issuing the rake jasmine:ci command:

➜  calendar git:(jasmine) rake jasmine:ci
 Waiting for jasmine server on 58538...
 Waiting for jasmine server on 58538...
 Waiting for jasmine server on 58538...
 [2011-11-19 23:38:14] INFO  WEBrick 1.3.1
 [2011-11-19 23:38:14] INFO  ruby 1.9.2 (2011-07-09) [x86_64-linux]
 [2011-11-19 23:38:14] WARN  TCPServer Error: Address already in use - bind(2)
 [2011-11-19 23:38:14] INFO  WEBrick::HTTPServer#start: pid=6921 port=58538
 Waiting for jasmine server on 58538...
 jasmine server started.
 Waiting for suite to finish in browser ...
 .........

 Finished in 1.17 seconds
 9 examples, 0 failures

It is wonderful to have a reproducible test environment for all team members as well as for our continuous integration server. This will save hours upon hours of tracking down idiosyncrasies between different environments.

The other benefit of running the jasmine server is that it is a server. To see this in action, we can start the server with the rake jasmine (omitting the :ci from the continuous integration version of the command):

➜  calendar git:(jasmine) rake jasmine
your tests are here:
  http://localhost:8888/

 [2011-11-19 23:58:02] INFO  WEBrick 1.3.1
 [2011-11-19 23:58:02] INFO  ruby 1.9.2 (2011-07-09) [x86_64-linux]
 [2011-11-19 23:58:02] WARN  TCPServer Error: Address already in use - bind(2)
 [2011-11-19 23:58:02] INFO  WEBrick::HTTPServer#start: pid=7077 port=8888

And, as the output instructs us, we can find our specs at http://localhost:8888/. At this point, nothing prevents us from making real HTTP request of an application server also listening on localhost. We are no longer restricted to simulating browser interaction. We can test the real thing.

Tests are only useful when run on each commit. Unless you have the kind of team that is intensely committed to manually running the Jasmine suite before each commit, you will need a continuous integration server.

For ruby shops, the jasmine gem includes rake [34] commands that can be used in continuous integration. Instead of running the server manually with rake jasmine, a continuous integration server would invoke the rake jasmine:ci command. The exit status and output from this command work nicely with most continuous integration environments.

The one caveat with using the jasmine gem for continuous integration is that the jasmine gem needs to start up an actual web browser to execute the tests. This can be difficult to configure. There are headless alternatives to the jasmine gem. The jasmine-headless-webkit [35] is a good starting place. The PhantomJS [36] javascript environment is another. The latter even includes a run-jasmine.js script which is easily adaptable for use in a continuous integration environment. Of the two, PhantomJS is currently better suited for Backbone development, but both are undergoing active development.



[32] The toHaveText() matcher comes from the jquery-jasmine plugin

[33] At the time of this writing Firefox is the browser that works best with Jasmine

[34] Rake is the ruby equivalent of the venerable Unix command, make