Testing Backbone + RequireJS Applications with Jasmine


In my previous post, I covered the structure of a Backbone.js application using RequireJS.
The next thing (or if you are a TDD fan, the first thing) we do is to run some tests on it.
According to Coda Hale:

Writing tests for your code may not reduce the number of bugs, but it will make fixing the bugs you inevitably find easier.

Obviously, we can take advantage of the AMD architecture, to help us write modular tests (or “specs” in the BDD language).
To get a better sense of the challenges and different approaches in unit testing, I wrote the exact same tests three times using three different testing frameworks: Jasmine, Mocha and QUnit.

The Concept

The concept is fairly simple. We want to test each one of our modules (Models, Collections, Views) and validate they behave as expected.
We will have one html file, on SpecRunner javascript file, which will initialize the test suite, and a spec file per AMD module.

Since the concept and the implementation are so similar for the 3 frameworks, I’ll focus on Jasmine in this post. the code for the 3 of them is available in the GitHub repo.

index.html

This is how our Jasmine html file looks like. there’s not a lot I can say about this.

I added a hidden (or almost hidden) element with the id “sandbox” where all the DOM elements will be appended to while testing,
each view test will append itself into this element, and will remove itself when done testing. I kept this element visible with very little height so a selector such as jQuery.is(“:visible”) will be valid. There are many other ways to accomplish this.



Jasmine Spec Runner
<script src="/js/lib/require-2.0.5.js" data-main="SpecRunner"></script>
<div id="sandbox" style="overflow: hidden; height: 1px;"></div>

SpecRunner.js

Our spec runner is where the magic happens.
It is responsible to

  • Load the dependancies (core libraries, testing libraries)
  • Configure the testing environment
  • Load the test suites
  • Run the test engine

Configure RequireJS

One important bit is we would like to keep our require.config with the same paths as our app, so we won’t need to change anything within our application modules.
we will add an extra path to our spec (tests) folder, and keep the previous baseURL and paths identical to our app.js.
Because our baseURL is ‘/js/’, all the other paths will be relative to that.
One more thing I added is a cache buster to all the module urls using the ‘urlArgs’ parameter. It’s a good idea to have this in the test environment, so we don’t need to worry about browser caching.

require.config({
  baseUrl: "/js/",
  urlArgs: 'cb=' + Math.random(),
  paths: {
    jquery: 'lib/jquery-1.8.0',
    underscore: 'lib/underscore-1.3.3',
    backbone: 'lib/backbone-0.9.2',
    'backbone.localStorage': 'lib/backbone.localStorage',
    jasmine: '../test/lib/jasmine',
    'jasmine-html': '../test/lib/jasmine-html',
    spec: '../test/jasmine/spec/'
  },
  shim: {
    underscore: {
      exports: "_"
    },
    backbone: {
      deps: ['underscore', 'jquery'],
      exports: 'Backbone'
    },
    'backbone.localStorage': {
      deps: ['backbone'],
      exports: 'Backbone'
    },
    jasmine: {
      exports: 'jasmine'
    },
    'jasmine-html': {
      deps: ['jasmine'],
      exports: 'jasmine'
    }
  }
});

Configure and execute the test engine

Next, we will load our main dependencies using require() and setup Jasmine, require() our specs, and run Jasmine’s test engine.

require(['underscore', 'jquery', 'jasmine-html'], function(_, $, jasmine){

  var jasmineEnv = jasmine.getEnv();
  jasmineEnv.updateInterval = 1000;

  var htmlReporter = new jasmine.HtmlReporter();

  jasmineEnv.addReporter(htmlReporter);

  jasmineEnv.specFilter = function(spec) {
    return htmlReporter.specFilter(spec);
  };

  var specs = [];

  specs.push('spec/models/TodoSpec');
  specs.push('spec/views/ClearCompletedSpec');
  specs.push('spec/views/CountViewSpec');
  specs.push('spec/views/FooterViewSpec');
  specs.push('spec/views/MarkAllSpec');
  specs.push('spec/views/NewTaskSpec');
  specs.push('spec/views/TaskListSpec');
  specs.push('spec/views/TaskViewSpec');

  $(function(){
    require(specs, function(){
      jasmineEnv.execute();
    });
  });

});

The structure of a test suite

Jasmine, like most frameworks has a ‘beforeEach’ and ‘afterEach’ methods, which will run before and after each test.
In this spec, we test our ‘remaining items counter’ view, so, in our beforeEach method, we want to initialize the view and the todos collection the view consumes. The afterEach will remove the view from the DOM.

Jasmine also integrates ‘spies’ that permit many spying, mocking, and faking behaviors.

Async

One significant difference between Jasmine and Mocha is the way Jasmine handles async specs.
Instead of using a done() function in the test’s signature, Jasmine uses run(), wait(), and waitFor().
At first, this looked over complicated to me when compared to mocha’s simple approach, but once I got used to it, I’ve learned to appreciate the flexibility it gives you. For instance, in these cases your spec (or before/after) has more than one async call.
I know what you’re thinking. You should never have more than one async call per spec. I agree, but life is not ideal, and implementations shouldn’t set the limits.

Since a test suite will never be a dependency, and the Jasmine’s describe() method, that defines a test suite, already has a modular structure, there is not real need of wrapping it in a define statement.

describe('View :: Count Remaining Items', function() {

  beforeEach(function() {
    var flag = false,
    that = this;

    require(['models/Todo', 'views/CountView'], function(Todo, View) {
      that.todos = new Todo.Collection();
      that.view = new View({collection: that.todos});
      that.mockData = { title: 'Foo Bar', timestamp: new Date().getTime() };
      $('#sandbox').html(that.view.render().el);
      flag = true;
    });

    waitsFor(function() {
      return flag;
    });

  });

  afterEach(function() {
    this.view.remove();
  });

  describe('Shows And Hides', function() {

    it('should be hidden', function() {
      expect(this.view.$el.is(':visible')).toEqual(false);
    });

    it('should toggle on add', function() {
      this.todos.add(this.mockData);
      expect(this.view.$el.is(':visible')).toEqual(true);
    });

  });

  describe('Renders Text', function() {

    it('should be empty', function() {
      expect(this.view.$el.text()).toEqual("");
    });

    it('should re-render on add', function() {
      this.todos.add(this.mockData);
      expect(this.view.$el.text()).toEqual("1 item left");

      this.todos.add([this.mockData,this.mockData]);
      expect(this.view.$el.text()).toEqual("3 items left");
    });

  });

});

You get the point.
The full running Jasmine test suite of the Todos app can be found here.
The source code for this test suite can be found here

Edit, Jan 2013:
Pointed by Andreas Boehrnsen, a better way to write the specs would be to define the dependencies in a define block.

define(['models/Todo', 'views/CountView'], function(Todo, View) {

  return describe('View :: Count Remaining Items', function() {

    beforeEach(function () {
      that.todos = new Todo.Collection();
      that.view = new View({collection: that.todos});
      that.mockData = { title: 'Foo Bar', timestamp: new Date().getTime() };
      $('#sandbox').html(that.view.render().el);
    });

    // ...

  });

});

The async part remains a good tool to have for other async cases.

Conclusions

RequireJS not only help handle dependencies and code organization in applications, it also makes testing easier and more fun.
Modular code is clearly broken in to “units” which makes it very easy to unit test.

Lead UI Engineer and a Software Architect.
Over 12 years of professional experience, writing complex web applications, and still learning something new every day.
Currently working for Okta in San Francisco

Twitter LinkedIn Google+ 

18 thoughts on “Testing Backbone + RequireJS Applications with Jasmine

  1. Joe

    There seem to be a fees ways to skin this cat but I think you are right to use waitsFor. A general note about your spec descriptions though, make your test descriptions more specific. If I read those describes with no knowledge of the test code I’d be utterly lost, they return very little business value.

    Reply
  2. Pingback: DRY Testing of Require.js based Backbone apps using Jasmine | Ted Naleid

  3. Pingback: 3 Tips For Writing Better Backbone Views | Simple Thoughts

  4. Andreas Boehrnsen

    Hi Uzi,

    thanks a lot for the writeup. That was helpful.
    I was just wondering why you take this approach with the “required” block. It seems quite complicated to me.

    Wouldn’t it be easier to wrap it into a “define” block and to pass the dependencies with it? Then the code would be executed when the dependencies are loaded. No need for the async “hack”.

    Would be interesting to hear why you chose this route.

    Thanks
    Andreas

    Reply
    1. Uzi Kilon Post author

      Andreas , Thats a very good point.
      I guess this is more of an experiment and it could be done in better ways.
      I was messing with async functionality support on the client side unit testing, and this shows that it’s something that can be done fairly easily.
      I don’t find this a ‘hack’. The nature of the beast will always require some async functionality in almost every web app, and it’s good to know there’s a path that supports it.

      Reply
  5. Kris

    Hi, thanks for some good tips, I’ve been struggling with this for a while now. I started with wrapping my tests with a require-block which had the benefit of not having to wait for the dependencies to load. But when I wanted to test the more complex objects with dependencies that I wanted to mock, I had to fall back to this. Actually used this helper to wrap the wait-code https://github.com/scottburch/jasmine-require. Then I could define mocks using sinon before the requireDependencies call, turned out nice and clean!

    Reply
  6. Keith

    I’m just curious how you would do handle the require config if you were using jasmine-node as a global nodejs module? I am continuously getting “define is not defined” when trying to use define in my Jasmine specs with node and backbone, which leads me to believe that my require dependencies are incorrect. However, when you are using jasmine-node globally, you do not have jasmine.html or even the js file to implicitly tell require where to look (it’s not in your project folder)

    Reply
  7. Pingback: Using Jasmine with Require | Mabe's Butterdish

  8. Pingback: Running Jasmine Tests With PhantomJS | Simple Thoughts

  9. vinngn

    Thanks Uzi for your great post. I am trying to do the same thing with Jasmine 2.0 and running into the problem bellow. I am not sure if you’re interested in helping me looking into making it work for new version of Jasmine. It will be interested.

    Thanks,
    Vin

    require(['underscore', 'jquery', 'jasmine-html'], function(_, $, jasmine){

    var jasmineEnv = jasmine.getEnv();
    jasmineEnv.updateInterval = 1000;

    // I am keep getting options is undefined with new jasmine.HtmlReporter();
    var htmlReporter = new jasmine.HtmlReporter();

    jasmineEnv.addReporter(htmlReporter);

    jasmineEnv.specFilter = function(spec) {
    return htmlReporter.specFilter(spec);
    };

    var specs = [];

    specs.push(‘spec/models/TodoSpec’);
    specs.push(‘spec/views/ClearCompletedSpec’);
    specs.push(‘spec/views/CountViewSpec’);
    specs.push(‘spec/views/FooterViewSpec’);
    specs.push(‘spec/views/MarkAllSpec’);
    specs.push(‘spec/views/NewTaskSpec’);
    specs.push(‘spec/views/TaskListSpec’);
    specs.push(‘spec/views/TaskViewSpec’);

    $(function(){
    require(specs, function(){
    jasmineEnv.execute();
    });
    });

    });

    Reply
  10. Pingback: Backbone, Links And Resources (2) | Angel "Java" Lopez on Blog

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>