Fast tests mean a fast feedback loop when doing TDD. Let’s make our tests fast with Guard.
A Jasmine spec in CoffeeScript:
describe 'A suite', ->
it 'contains spec with an expectation', ->
expect(true).toBe(true)
Gemfile:
source 'https://rubygems.org'
gem 'jasmine'
gem 'guard'
gem 'guard-coffeescript'
gem 'guard-livereload'
Guardfile:
guard :coffeescript, output: 'javascripts' do
watch(%r{^src/(.*)\.coffee})
end
guard :coffeescript, output: 'spec/javascripts' do
watch(%r{^spec/src/(.*)\.coffee})
end
guard 'livereload' do
watch(%r{^spec/javascripts/.*/(.*)\.js})
watch(%r{^spec/javascripts/(.*)\.js})
watch(%r{^javascripts/.*/(.*)\.js})
watch(%r{^javascripts/(.*)\.js})
end
This compiles CoffeeScript, watches our specs or Javascript files for changes, and reloads our browser automatically.
Start a jasmine server:
rake jasmine
Visit localhost:8888. See the results of the specs.
Change the specs or Javascript files and the browser will automatically reload.
I’ve been doing a bit more Javascript development as of late and am using Jasmine for my unit tests. It’s been wonderful. I did find that it was a bit of a bear to test shared behavior (there’s a couple levels of inheritance in the Backbone models) so I decided to whip up a crude implementation of shared behavior.
appNamespace.jasmine.sharedExamples = {
"rectangle": function() {
it("has four sides", function() {
expect(this.subject.sides).toEqual(4);
});
},
"square": function() {
it("has the same width and height", function() {
expect(this.subject.width).toEqual(this.subject.height);
});
},
"an object with area": function(options) {
it("calculates area correctly", function() {
expect(this.subject.area()).toEqual(options.area);
});
}
};
I’ve declared a handful of shared examples in an object I’ve made available to
my Jasmine suite. Next, to define itShouldBehaveLike:
window.itShouldBehaveLike = function() {
var exampleName = _.first(arguments),
exampleArguments = _.select(_.rest(arguments), function(arg) { return !_.isFunction(arg); }),
innerBlock = _.detect(arguments, function(arg) { return _.isFunction(arg); }),
exampleGroup = appNamespace.jasmine.sharedExamples[exampleName];
if(exampleGroup) {
return describe(exampleName, function() {
exampleGroup.apply(this, exampleArguments);
if(innerBlock) { innerBlock(); }
});
} else {
return it("cannot find shared behavior: '" + exampleName + "'", function() {
expect(false).toEqual(true);
});
}
};
This uses a handful of Underscore.js functions to slice and dice arguments and
expects our appNamespace.jasmine.sharedExamples object to exist.
This checks to see that our example group exists, and if it doesn’t, it creates a failing test case warning us about it. If the example group does exist, it’ll wrap the behavior in a describe block (the property of the object being the describe string), pass any additional arguments, and finally execute any additional block of code.
Based on the shared examples above, we could write:
describe("shapes", function() {
describe("rectangle", function() {
beforeEach(function() {
this.subject = new Rectangle({ width: 5, height: 10 });
});
itShouldBehaveLike("rectangle");
itShouldBehaveLike("an object with area", { area : 50 });
itShouldBehaveLike("square", function() {
beforeEach(function() {
this.subject.set({ width: this.subject.height });
});
});
});
describe("square", function() {
beforeEach(function() {
this.subject = new Rectangle({ width: 5, height: 5 });
});
itShouldBehaveLike("rectangle");
itShouldBehaveLike("an object with area", { area : 25 });
itShouldBehaveLike("square");
});
});
What have you written to make unit-testing Javascript easier?