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.
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:
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.
The basic implementation of my ‘toHaveSelectedImageAt’ matcher uses the same code that I originally had in my specs.
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.