Testing Backbone with Jasmine and Sinon
This is based on an XKCD leanback web app I’m working on that will form part of a html5 Boxee app. Think sitting on your couch, remote control in hand flicking through XKCD comics on the TV.
It brings together quite a few technologies and techniques. The end results is something that is separated out nicely (no spaghetti code) that is simple, testable, documented and maintainable.
The Specs
describe('Comic Navigator', function() {
var nav = new Xkcd.ComicNavigator()
var latestComic = new Xkcd.Comic({ id: 101 })
describe('when navigating to the latest xkcd comic', function() {
beforeEach(function() {
this.fetchStub = sinon.stub(Xkcd.Comic.prototype, 'fetch').yieldsTo('success', latestComic)
nav.toLatest()
})
afterEach(function() {
this.fetchStub.restore()
})
it('should fetch the latest comic. NB requests without a comic number get the latest comic.', function() {
expect(this.fetchStub.thisValues\[0\].get('id')).toBeFalsy()
})
it('should navigate to the latest comic', function() {
expect(nav.get('current')).toBe(latestComic)
})
it('should keep track of what the latest comic is', function() {
expect(nav.get('latest')).toBe(latestComic)
})
})
})
So what’s going on?
Jasmine is the BDD framework that is giving us that familiar ‘describe/it’ way of defining the behaviour and at the code level the expect-ations. It’s easy to understand and get started with. It doesn’t have to be run in the browser as it has no dependency on the DOM, so is CI friendly too.
Sinon.JS is the mocking framework. It’s only being used here to stub out the fetch call, which would otherwise make a call to the server. It also keeps the call synchronous and gives us a canned answer that we can later observe. It’s framework independent, so can be used with your favourite Javascript testing framework.
The Feature
var Xkcd = (function(){
var ComicNavigator = Backbone.Model.extend({
toLatest: function(){
var comic = new Comic()
var nav = this;
comic.fetch({
success: function(model){
nav.set({"current": model});
nav.set({"latest": model});
}
});
}
});
var Comic = Backbone.Model.extend({
url: function(){ return 'https://dynamic.xkcd.com/api-0/jsonp/comic/' + (this.id || "") + '?callback=?' },
initialize: function(){
this.backbone\_fetch = this.fetch;
this.fetch = function(options){ options || (options = {});
options.jsonpCallback = 'comicIncoming';
return this.backbone\_fetch(options);
}
}
});
return { ComicNavigator: ComicNavigator, Comic: Comic };
})();
So what’s going on?
First of all, I’m using the Revealing Module Pattern to namespace the models into ‘Xkcd’. The return statement at the end is effectively publicly exposing the ComicNavigator and Comic. Anything I want to keep private I can, I just don’t expose it. This particular version of the Module Pattern is quite neat as it clearly shows what’s public and you can use consistent function definitions regardless of whether the function is public or private.
Backbone.js is a lightweight MVC-like framework that allows you to structure your code, specifically separating your data from its presentation. I’m doing it an injustice, it actually does a lot more and is very useful in keeping everything in sync with a RESTful backend, not to mention routing, eventing and validation.`
In the above example I have 2 models. The rest of the code uses routes for bookmarking specific comics and possibly navigation to (I’ve yet to decide on that). There’s also a view which will bind to the current comic on the navigator. As the navigator changes the current comic the UI will automatically reflect this. You’ll be able to see all this in the finished app (coming soon!).
For the Comic model, Backbone has a fetch method that will automatically populate the model, given a url. I’m overriding the url property in order to add a jsonp callback (the XKCD service is on a different domain to the app). Just by adding “?callback=?” means backbone will switch to jsonp. Backbone just defers to jQuery’s $.ajax (or Zeptos) method, which automatically detects the presence of =? and switches from json to jsonp. If there’s no comic id the service returns the latest comic. I’m also intercepting the fetch function to specify ‘comicIncoming’ as the callback, normally this isn’t necessary but the XKCD service doesn’t like jQuerys automatically generated callback.
At this stage, I’m not certain that the Navigator should be a Backbone model. It makes sense in a typical MVC app and has some characteristics of a Backbone model but then doesn’t have the persistence/entity behaviours.
I’m also not happy with the 1st spec, I think it’s highlighting a smell around newing up a comic. My static mind says inject a factory but that doesn’t feel right in a dynamic language. Thoughts?
What I’ve shown is a small slice of the app, to show off some of the techniques and tools. Clearly there’s more, I just haven’t written it yet. Once the app is complete, it will show how the other parts come into play, specifically the routes and the view.
To Recap
- Module Pattern for namespacing and keeping things private
- Jasmine for BDD style testing ala RSpec
- Sinon for test doubles (mocks, stubs etc if you care about the terms which cause more confusion than their worth)
- Backbone for separating code into different concerns
- Jsonp with Backbone to fetch data from a different domain
The finished xckd couch app, the html5 boxee app and the backbone on the couch plugin that came out of all of it.