Ensure an explicit number of expectations within a spec in Jasmine

javascript, testing, jasmine
This post is old, and probably obsolete

QUnit and js-test-driver have ways of specifying how many assertions will be executed in a test case (expect and expectAsserts respectively) but Jasmine doesn't. But it can still be done - here's how.

Why would anyone want to count the number of expectations in a test

Usually Jasmine specs are pretty straightforward (here systemUnderTest is defined somewhere else):

it("does something really simple", function () {
  var actual = systemUnderTest.methodToTest(arg);
  expect(actual).not.toBe(true);
});

There are no branches, and you know Jasmine is going to run through all the expectations, one after the other. But it gets more complicated when loops, or async specs are involved. For example

it("throws exceptions for all wrong arguments", function () {
  var methodWrapper = function (arg) { systemUnderTest.methodToTest(arg) };
  var wrongArgs = [
    new Date(),
    Math.random(),
    false,
    null,
    {a: 1, b: 2},
    "a string",
    [1, 2],
    undefined,
    function () { return 123; }
  ];

  wrongArgs.forEach(function (arg) {
    expect(methodWrapper.bind(null, arg)).toThrow();
  });

});

Here there is an array of arguments, each of which should cause the method systemUnderTest.methodToTest to throw an exception. So we loop through the array of arguments, and run an expectation for each of them. We have to wrap the call to methodToTest in a function, because it's going to throw an exception - the function methodWrapper is a partial used by .bind to generate such a function. So each iteration of the loop will have a new version of methodWrapper, with arg already plugged in. Then expect can run it, catch the exception, and pass the test.

This is still simple enough, there is only one loop with one statement in it. But you can see how this could easily get out of hand. What if the code was a little more complex, and for some reason the expectations in the loop are not executed? It will look like the test has passed, and you will never know the difference.

The js-test-driver page gives another example, using workers (adapted to Jasmine)

it("shows what could go wrong", function () {
  var worker = new Worker();
  var doSomething = {};

  worker.listener = function (work){
    expect(work).toBe(doSomething);
  };
  worker.perform(doSomething);
};

If the worker doesn't call the callback function, then the expectation will not be run, and once again, it will look like the test has passed when it hasn't.

Defining the number of expected assertions in Jasmine

Jasmine doesn't have a simple way to tell how many assertions have run, but digging into the code I found where to get the information - in version 1.3 at least: this.env.currentSpec.results_.passedCount

Therefore the examples above can be rewritten as

it("throws exceptions for all wrong arguments", function () {
  var methodWrapper = function (arg) { systemUnderTest.methodToTest(arg) };
  var wrongArgs = [
    new Date(),
    Math.random(),
    false,
    null,
    {a: 1, b: 2},
    "a string",
    [1, 2],
    undefined,
    function () { return 123; }
  ];

  wrongArgs.forEach(function (arg) {
    expect(methodWrapper.bind(null, arg)).toThrow();
  });

  expect(this.env.currentSpec.results_.passedCount).toEqual(wrongArgs.length)
});

and

it("shows what could go wrong", function () {
  var worker = new Worker();
  var doSomething = {};

  worker.listener = function (work){
    expect(work).toBe(doSomething);
  };
  worker.perform(doSomething);
  expect(this.env.currentSpec.results_.passedCount).toBe(1);
};

A note on compatibility

This will work, but only in version 1.3 - with the new 2.0 version currentSpec remains private.

There was a plugin, jasmine-intercept, that used to take care of that, but apparently it has now been superseded by where.js. I haven't had a chance to look into that yet, for now I'll stick to my quick and dirty workaround.