Custom Jasmine Matchers For Clarity In Testing Backbone.js Models

I’ve been writing some Jasmine specs for a sample Backbone.js application that I’m building. The application is a simple image gallery, and one of the features is the ability to navigate to through the image list with ‘next’ and ‘previous’ arrows. The code to move to the next or previous image is not terribly difficult, but there are a few edge cases to cover – such as what happens when no image is selected and you call ‘next’, or when you’re on the first image in the list and you call ‘previous’. I wanted to make sure I covered these cases in my specs, to make sure I didn’t have any regression failures as additional features were added to the application.

Duplicate Code In My Specs

In the process of writing up specs for the image navigation, I found myself repeating a few lines of code in each of them to check the index of the currently selected image.

describe("when no image is selected and previous is called", function(){
  beforeEach(function(){
    this.images.previous();
  });

  it("should select the last image", function(){
    var lastImage = imageList.length-1;
    var selectedImage = this.images.indexOf(this.images.selectedImage);
    expect(selectedImage).toBe(lastImage);
  });
});

Lines 7, 8 and 9 of this gist show what I was doing to check the index of the selected image against what I expected. This code works and it got me through my first set of tests. I quickly got tired of writing the same lines of code for every test while only varying the actual and expected indexes, though.

In addition to the duplicated code, a failing test (when the index was not what I expected) would produce an error message like “Expected 0 to 1″. This is not terribly informative… what do “0″ and “1″ mean? If I wasn’t in the code already working with it, I’d have to go examine the failing code to be able to answer that.

Custom Jasmine Matchers FTW!

To reduce the code duplication and provide a better error message, I decided to write a custom jasmine matcher. The end result allowed me to have spec code that looks like this:

describe("when no image is selected and previous is called", function(){
  beforeEach(function(){
    this.images.previous();
  });

  it("should select the last image", function(){
    expect(this.images).toHaveSelectedImageAt(imageList.length-1);
  });
});

Line 7 of this gist shows the new matcher in action.

Note the name of the matcher that I created: ‘toHaveSelectedImageAt’. Not only have I reduced the amount of cod (and code duplication) to check the index, but I’ve also introduced application-specific language into the syntax of my tests! When I read this test now, I read it in my mind as “i expect this image collection to have a selected image at index #’. This provides much greater understanding of what the matcher does and is looking for / checking against.

Also note that I’m calling the ‘expect(…)’ method on my images collection, directly, and then telling my matcher method what index I expect to see for the selected image. Since the images collection knows which image is selected, there’s no need for me to pull the index out of the collection in my spec, directly. I can let the custom matcher do this for me and simply tell the matcher what index I’m expecting the currently selected image to be at.

Lastly, the error message that I receive when a test fails has been customized to say: “Expected selected image index of 0 to be 1″. Again, this gives me application-specific information instead of just raw numbers. Now when my tests fails, I can see exactly what was being checked and have some context to interpret the numbers “1″ and “0″ in the error message.

Implementing toHaveSelectedImageAt

The basic implementation of my ‘toHaveSelectedImageAt’ matcher uses the same code that I originally had in my specs.

beforeEach(function() {
  this.addMatchers({

    toHaveSelectedImageAt: function(expectedIndex) {
      var images = this.actual;
      var actualIndex = images.indexOf(images.selectedImage);
      this.actual = actualIndex;
      this.message = function(){
        return "Expected selected image index of " + actualIndex + " to be " + expectedIndex;
      };
      return (actualIndex == expectedIndex);;
    }

  });
});

In addition to the index checking code, I’m modifying a few properties of the matcher object to provide better context and information for the error messages.

“this.actual” is initially set by the object that you pass into the ‘expect’ method call. It’s used to report the actual value found by your test, if you don’t provide a custom error message. I’m sure there are other uses for this, but that’s the most basic use I’ve seen so far.

The “this.message” function returns the error message that I want. I’m not entirely sure why this needs to be a function, but it does need to be. You’ll get error messages about not being able to call “apply” if it isn’t a function.

Last, the function needs to return a boolean to specify whether or not the test passed. True == passed, false == failed. When a test fails, the message function is called to retrieve the message that is displayed.

If you’d like to see all of this code in one place, it’s available on this github gist.

Not Just For Backbone Objects

The truth about this example is that I’m only incidentally using Backbone.js models and collections. Any time you have repetitious code in your specs’ expectations, error messages that offer no insight into the application’s functional failures, or plain-old ugly code to set up and assert your expectations, you should consider creating a custom matcher. And, if you can add application-specific language into that matcher’s method name and error message while you’re at it, you’ll be sitting in a much better position a few days / weeks / months down the road when you have to revisit this code and remember / re-learn what you had previously built.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Behavior Driven Development, Javascript, Testing. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Chris matheson

    Hi Derick, great post. Got my matcher up and running. Do you know of a way to add matchers for a set of specs altogeather? to the global jasmine object kind of thing?