Avoiding Common Errors in Your Jasmine Test Suite

Working with Javascript can be, at best, a mildly frustrating experience. As I've started to work more with Jasmine and Backbone.js, I've added some simple points to my Javascript checklist that make the experience more palatable.

When I began using these tools, I found myself spending inordinate amounts of time figuring why my tests were failing instead of keeping to a short red-green-refactor cycle time. The following is a contrived example, but it does illustrate how easy it is to create errors that are hard to track down as you begin to use Jasmine.

Let's say you're working on a simple Todo Rails app using Backbone.js. You'll need a collection for your Todos, so you write a spec to require a certain model. Here's the first spec:

1     describe("Todos Collection", function () {
2       it("uses the Todo model", function () {
3         var todos = new Todo.collections.Todos();
4         expect(todos.model).toEqual(Todo.models.Todo);
5       });
6     });
Jasmine failures for the Todos Collection
TypeError: Cannot read property 'Todos' of undefined; Cannot read property 'model' of undefined

OK, we've got some failures - "TypeError: Cannot read property 'Todos' of undefined". We don't have a model yet, so we go create one:

1 namespace("Todo.models", {
2   Todo: Backbone.Model.extend({})
3 });

All we really need to do is extend Backbone.Model without new options - we just want to define Todo.models.Todo for the sake of our test. We add this code and run the specs again:

Jasmine failures for the Todos Collection
TypeError: Cannot read property 'Todos' of undefined; Cannot read property 'model' of undefined

Hmm, so we get the same failures even though we just defined the Todo.models.Todo. It turns out that for this case, jasmine.yml is the source of our frustration:

1 src_files:
2   - ...
3 
4   - public/javascripts/**/*.js

We're including all of our public/javascripts files, but we're still missing our model definition. The problem is that when you generate jasmine.yml with the Jasmine gem, it doesn't know about any of the dependencies your Javascript code has. In our first spec, we need to load the model before we load the collection. If you change your jasmine.yml to look like this:

1 src_files:
2   - ...
3 
4   - public/javascripts/models/*.js
5   - public/javascripts/collections/*.js
6   - public/javascripts/**/*.js

You will enforce the loading of your models, then your collections, and then everything else. After this change, we finally get a proper failure in Jasmine:

Jasmine failures for the Todos Collection
Expected Function to equal Function

And then we can write the following code to make the test pass:

1     namespace("Todo.collections", {
2       Todos : Backbone.Collection.extend({
3         model: Todo.models.Todo
4       })
5     });

On to test #2 - ensuring that the url for the collection is "todos":

 1     describe("Todos Collection", function () {
 2       beforeEach(function () {
 3         this.todos = new Todo.collections.Todos();
 4       });
 5 
 6       it("uses the Todo model", function () {
 7         expect(this.todos.model).toEqual(Todo.models.Todo);
 8       });
 9 
10       it("url is 'todos'", function () {
11         expect(this.todos.url)toEqual("todos");
12       });
13     });
Jasmine failures for the Todos Collection
Everything's green - time to deploy!

Wait, 0 specs? Jasmine doesn't help you out much here, which is why I pretty much always have the Javascript console open in Chrome (or Firebug in Firefox).

Jasmine failures for the Todos Collection
Uncaught SyntaxError: unexpected identifier

We see there's an unexpected identifier on line 11, and I notice that I omitted the '.' in the second test. I add that in and my test fails for the right reason.

Jasmine failures for the Todos Collection
Expected undefined to equal 'todos'

I add the url definition to my collection, and run my specs again.

1 namespace("Todo.collections", {
2   Todos : Backbone.Collection.extend({
3     model: Todo.models.Todo
4     url: "todos"
5   })
6 });
Jasmine specs for the Todos Collection
TypeError: Cannot read property 'Todos' of undefined; Cannot read property 'model' of undefined

Ah geez, failures all over. Jasmine is a little more helpful by explicitly showing the failing tests, but it's the console again that points you to the problem. In this case, I'm missing a comma at the end of the 'model: Todo.models.Todo' line in my collection. I add it in and my tests turn green.

After all that, I finally have a scant two specs passing for my Todos collection. The time it takes to troubleshoot these errors can be frustrating, and it's really only the beginning of issues you can have just using Javascript in general, let alone trying to test drive your Javascript code. If you:

  1. Run Jasmine with the browser's Javascript console open
  2. List your source files in the correct order inside jasmine.yml

Then you can start spending less time debugging and more time test driving.

Mike Jansen, Craftsman Management

Mike Jansen has spent the past seven years in pursuit of quality, maintainable code in the world of software development.