Custom QUnit Assertions

QUnit is a popular testing framework for JavaScript, and it’s included in the box when you generate a new Ember CLI project. In this post, we’ll use custom assertions to improve the legibility of our tests and their results.

Compared with competing frameworks, QUnit offers a rather small set of assertions. If you limit yourself to using only these, checking anything beyond equivalence can get a bit awkward:


  assert.ok(this.$('h2').hasClass('tilt'), 'header is tilted');
  assert.ok(/^\a+/.test(word), 'word begins with alpha chars');
  assert.ok(/foo/.test(description), 'word contains foo');
  assert.ok(num > 1, 'number is > 1');

It’s not difficult to understand what’s going on here, but it’s not as easy as it should be. I much prefer statements like these:


  assert.hasClass(this.$('h2'), 'tilt');
  assert.matches(word, /^\a+/);
  assert.contains(description, 'foo');
  assert.gt(num, 1);

The maintainers of QUnit have decided not to provide methods like this, but given how easy they’ve made it to write your own, I don’t really mind.

Here’s How

To log an assertion, we need only call QUnit’s pushResult method. You could do this anywhere, but a convenient convention is to place new assertion helpers right next to the existing ones on the global QUnit object. Here’s an example:


QUnit.assert.hasClass = function( a, b, message ) {
  this.pushResult( {
      result: a.hasClass(b),
      actual: a.attr('class') || '',
      expected: b,
      message: message || `${a.prop('nodeName')} has class '${b}'`
  });
};

(If you’re looking for where to put this in an Ember CLI project, tests/test-helper.js is a good start.)

A Few Notes

  • The result parameter tells QUnit whether the assertion passed or failed; the other parameters are used for reporting.
  • Providing the actual and expected values can make your test output much more useful than what you get from assert.ok:

  1) [PhantomJS 2.1] example with assert.ok
     is flipped

     expected: true
       actual: false

     http://localhost:7357/assets/tests.js:319:14
     ...
  2) [PhantomJS 2.1] example with custom assertions
     H2 has class 'flip''

     expected: 'flip'
       actual: 'blink marquee tilt'

     http://localhost:7357/assets/tests.js:219:20
     ...
  • Because our custom assertions are more specific, we can often produce reasonably useful messages inside the assertion instead of requiring the caller to pass in English descriptions:

qunit_messages

Conclusion

This seems pretty trivial, but small investments in test tooling can pay dividends over time when you spend as much time reading and writing tests as we do. Have you written custom assertions to reduce friction in your tests? Do you prefer to use third-party libraries like Chai?

The custom assertions shown in this post can be found in this gist.

Further Reading: