Jasmine and Shared Examples

Josh Clayton

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?