Archive for August, 2010

Custom jQuery matchers in Jasmine

!!! THIS BLOG IS NO LONGER ACTIVE – CHECK OUT MY CURRENT BLOG AT VELESIN.IO !!!

Implementing your custom matchers (or custom assertions in case of xUnit-like frameworks) is a very good testing practice. In theory, as you’re verifying if a given expectation is correct, ‘.toBeTrue()’ is the only matcher you need. In practice however, using convoluted constructs like:

expect(someVariable != undefined).toBeTrue();

severely hinders readability of your tests. Thats why more clear and explicit matchers like:

expect(someVariable).toBeDefined();

come in handy. Such neat matchers are of course more comfortable to use, but their real power comes from revealing the intent of a test – what is one of primary goals (and strengths) of the whole Behavior Driven Development concept.

Different frameworks provide different sets of built in matchers. The one provided by Jasmine is relatively bare-bone, but it is not a bad thing, as it offers neat and robust mechanism for creating your own matchers when needed without polluting an API with a myriad of predefined matchers for all occasions (most of which you’ll never use). In this post I’ll show how to built a custom jQuery matcher library for Jasmine (provided by my jasmine-jquery plugin).

The simplest way to create a custom matcher, presented in Jasmine docs, is to use “.addMatchers()” method in your “beforeEach()” block:

beforeEach(function() {
  this.addMatchers({
    toHaveClass: function(className) {
      return this.actual.hasClass(className);
    }
  });
});

“this.actual” is automatically set by Jasmine to the variable on which we set expectations via “expect(…)” method. Other parameters for your matcher can be set as usual, as input parameters of your callback function (see “className” in above example). Your matcher function must return a boolean value indicating if it succeeded or not.

Now, when you have your first jQuery matcher defined, you can use it in your tests like this:

it('should have class "my-class"', function() {
  var myElement = $(<div class="my-class"></div>');
  expect(myElement).toHaveClass("my-class");
});

instead of much less readable:

it('should have class "my-class"', function() {
  var myElement = $('<div class="my-class"></div>');
  expect(myElement.hasClass("my-class")).toBeTruthy();
});

Jasmine also automatically allows your custom matchers to be inverted by prepending them with “.not”, so you can also use your freshly created jQuery matcher like this:

it('should have class "my-class"', function() {
  var myElement = $('<div class="my-class"></div>');
  expect(myElement).not.toHaveClass("other-class");
});

So far so good. Your tests are now much more readable and their intent is expressed more clearly.¬†However, when written in such a way (placed in “beforeEach()”), your matchers are local to your test file. In more specific cases this may be not so bad, but in case of e.g. jQuery related matchers, we will want to re-use them in many tests across many projects, so making a local copy each time is definitely not an option.

The solution to the above problem is simple – as you could see, “addMatchers()” method takes a JS object as a parameter. So instead of declaring this object locally, lets move it to a separate file:

in my_jquery_matchers.js:

jQueryMatchers = {
  toHaveClass: function(className) {...},
  toBeVisible: function() {...},
  ...
};

in my_spec.js:

beforeEach(function() {
  this.addMatchers(jQueryMatchers);
});

Now the matchers are neatly separated into their own file, allowing you to reuse this “library” among may test files. And as Jasmine allows you to use “beforeEach()” at arbitrarily high level – not only in your “describe” block but also at a level global to your whole test file, or even at a level of the test runner itself (global for all test files executed by this runner), you need to invoke “addMatchers()” only once, so your tests stay reasonably DRY.

When creating custom matchers specific to your code, above solution is sufficient. However, a matcher library for an external framework like jQuery feels more like a part of the test framework core, not as a part of test suite. Therefore, two problems that don’t bother me that much in case of my custom matchers, make me not fully satisfied in case of jQuery matcher library. First, variable containing matchers (jQueryMatchers in the above example)¬†pollutes global namespace. Second, having to instantiate a part of the core library inside one of your “beforeEach()” blocks is awkward – these blocks should rather contain code related to your SUT (system under tests), not a framework’s boilerplate.

So, let’s take it one step further and “hack” our jQuery matchers into Jasmine framework directly. The idea may seem scary at first, but it is really straightforward. Jasmine keeps all its core matchers in a separate object called “Matchers”. All you need to do is to add your new matchers directly to the “Matchers” object’s prototype instead of passing them via “addMatchers()” method:

jasmine.Matchers.prototype.toHaveClass: function(className) {
  return this.actual.hasClass(className);
};

Now the only thing you need to do is to include the file in a test runner (the same way you include Jasmine core or jQuery library) and our jQuery matchers are transparently available in all test files, without polluting the namespace or any of “beforeEach()” blocks. (I’d like to reiterate here that for matchers related to your custom SUT only, this may be a little overkill, but it is a neat and elegant solution when creating “core” matcher libraries, reused among many projects).

OK, we’re almost there with our solution, however, I’ll apply two more tweaks that will make our library more robust.

First annoyance to fix is the way Jasmine displays failed assertions for our jQuery matchers. Inside our matcher, this.actual is a jQuery element, and Jasmine by default can’t do anything smarter than dump it to string in an error message. So when you try:

var myElement = $(<div class="wrong-class"></div>');
expect(myElement).toHaveClass("some-class");

you’ll get:

Expected { 0 : HTMLNode, length : 1, context : HTMLNode, selector : '#sandbox', init : Function, jquery : '1.4.2', size : Function, toArray : Function, get : Function, pushStack : Function, each : Function,
...
a _LOT_ of similar jQuery element dump code here
...
, width : Function } to have class 'some-class'.

This makes an error message almost impossible to comprehend. However, it may be quite easily fixed by using a simple trick – modifying “actual” variable inside our matcher before returning:

jasmine.Matchers.prototype.toHaveClass: function(className) {
  var result = this.actual.hasClass(className);
  this.actual =
      $('<div />').append(this.actual.clone()).html();
  return result;
};

What we do with “this.actual” above, is conversion from jQuery element into a plain HTML string. This results in much more explanatory message, leaving no doubt why our test didn’t pass:

Expected '<div class="wrong-class"></div>' to have class 'some-class'.

The last problem left for us to solve is a situation when there is naming collision between our jQuery matchers and built-in Jasmine matchers. E.g. I’d like to have a matcher called “toContain()” that will check if a Query element contains another element, specified by a jQuery selector, but Jasmine already has a “toContain()” matcher that checks if an array contains specified item.

The simplest way to solve this issue is just to check what matcher names are already taken by Jasmine and to avoid potential collisions by renaming our jQuery matcher to e.g. “toContainElement()” or “toContainSelector()”; However, I’m quite stubborn and I want a simple “toContain()”. I’m also lazy, and I don’t want to check all built-in Jasmine matchers – and what’s even more important, I don’t want a naming collision when new matchers are added to Jasmine core in future.

Another simple trick will do it:

var builtInMatcher = jasmine.Matchers.prototype.toContain;

jasmine.Matchers.prototype.toContain = function() {
  if (this.actual instanceof jQuery) {
    // our jQuery matcher code here
    // return results
  }

  return builtInMatcher.apply(this, arguments);
};

If the tested variable is a jQuery element our jQuery matcher will be used, otherwise we pass the control to the original Jasmine matcher. Also, invoking original matcher by using “apply(this, arguments)” ensures that even if our overlapping jQuery matcher and original Jasmine matcher expect different number of arguments the above code will still work correctly.

And that’s all. The full, more polished implementation can be found in my jasmine-jquery library (it contains also support for HTML fixtures – see my previous post about fixtures in Jasmine for a more detailed overview).

Comments (3)