giant robots smashing into other giant robots

Written by thoughtbot

griffindy

Automatically Reload the Browser for Javascript Tests

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.

jdclayton

Jasmine and Shared Examples

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?