Assertions

Comparing 3rd party assertion libraries expect.js, should.js, etc.

Source code is a communication tool. It tells other developers what the machine will do to the input data. Sometimes the source code gets executed by an actual computer. The execution is fast, but human reading and understanding is slow. At some threshold, the communication breaks down and a bug is introduced because someone did not understand what the code meant to say.

There are syntax features that source code can use to express itself better. Naming functions and variables for example is a primary syntax feature used to make code's purpose clear:

1
2
3
4
5
6
// avoid this
var f0 = function (a, b) { return a + b; };
var tmp = f0(2, 2);
// do this
function add(a, b) { return a + b; }
var sum = add(2, 2);

Staticly typed languages (C, C++, Java) use type syntax to add meaning to the source code

int function add(int a, int b) { return a + b; }

JavaScript does not have type declarations, but the language's flexibility makes creating an assertion library to check types very easy. There are lots of different libraries that verify different properties. Using one or several libraries and sprinkling assertions throughout the code makes its meaning clear. Here are several libraries. Main comparison criteria:

  • expressiveness - how many different assertions are there?
  • pulse - is the project being actively maintained?
  • extensibility - can we add our own assertions?
  • ease of use - is it hard to start using it?

check-types

check-types by Phil Booth has two modes: checking and verifying. Any check method returns true or false. Any verify method throws an exception if a condition is false

1
2
3
4
check.verify.unemptyString('label-1', 'missing a label');
if (check.emptyObject(foo)) {
// foo == {}
}

check-types supports checking complex objects and iterating over a collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
check.verify.like({}, { foo: 'bar' }, 'Invalid object');
// Throws new Error('Invalid object')
check.map({
foo: 2,
bar: {
baz: 'qux'
}
}, {
foo: check.oddNumber,
bar: {
baz: check.unemptyString
}
});
// Returns { foo: false, bar: { baz: true } }

Can be extended by attaching methods directly to check object.

check-types shortcomings

  • Only a single message argument after predicate, thus requires building a string check.number(foo, 'foo should be a number, is ' + foo);
  • No throwing exception support - only really needed in the unit test code, not in production code.

expect.js

expect.js is single Jasmine-style expect(value)... function with chained assertions

1
2
3
4
expect(5).to.be.a('number');
expect('1').to.not.be(1);
expect([1, 2]).to.contain(1);
expect(window).to.have.property('expect')

Supports running functions and checking exceptions

1
2
3
4
expect(fn).to.throwException(function (e) { // get the exception object
expect(e).to.be.a(SyntaxError);
});
expect(fn).to.throwException(/matches the exception message/);

In general I use this library when unit testing using mocha. The syntax is very descriptive.

expect.js shortcomings

  • Has not been maintained in 2 years, lots of open issues and pull requests. There are lots of forks though.
  • No collection support or nested properties tests.
  • Lacks any additional information in the exception, seems geared towards the unit testing.
  • Conflicts with Jasmine's expect function

should.js

should.js is by express/jade/mocha's author TJ Holowaychuk so it has been extensively field-tested and very battle-hardened. Nice syntax and lots of built-in assertions. It extends Object.prototype with single non-enumerable property .should with chained assertions

1
2
3
4
5
6
var user = {
name: 'tj',
pets: ['tobi', 'loki', 'jane', 'bandit']
};
user.should.have.property('name', 'tj');
user.should.have.property('pets').with.lengthOf(4);

In some cases, a variable is not an Object (for example a primitive), so you need to use a static function to perform assertion

1
should(true).ok;

Optional error message can be passed into several assertions: eql, equal, within, instanceof, above, below, match, length, property, ownProperty.

Failed assertion generally has sensible information, for example

1
2
3
({foo: 'bar'}).should.have.property('zzz');
AssertionError: expected { foo: 'bar' } to have property 'zzz'
stacktrace ...

should.js shortcomings

  • Unclear how to add custom assertions, but the built-in coverage is great.
  • Uses expressions like foo.should.be.an.Array, breaking jshint and eslint (no unused expressions)
  • Breaks closure JavaScript minifier, mostly due to lots of expressions.

Jasmine matchers

I thought about using Jasmine with its collection of matchers in the production code, since we already use it in unit testing. Ultimately I rejected this approach, since this brings the entire Jasmine runtime.

contractual

Contractual exploits JavaScript label syntax quirk to check pre- and post- conditions. Because results of the expressions do not have to be used, you can write the following:

1
2
3
4
5
6
7
8
9
10
function divide (a, b) {
pre:
typeof a === 'number';
typeof b === 'number';
b !== 0, 'May not divide by zero';
main:
return a / b;
post:
__result < a;
}

If you run the above code, the preconditions will execute, but they will not affect the result, even if they fail. Contractual comes with a compiler that can transpile the above code into source code with actual assertions:

1
2
3
4
5
6
7
8
9
10
var OBLIGATIONS = require('obligations');
function divide(a, b) {
OBLIGATIONS.precondition(typeof a === 'number');
OBLIGATIONS.precondition(typeof b === 'number');
OBLIGATIONS.precondition(b !== 0, 'May not divide by zero');
var __result;
__result = a / b;
OBLIGATIONS.postcondition(__result < a);
return __result;
}

contractual shortcomings

  • Extra compiling step.
  • Generates 'precondition failed' message.
  • Cryptic error message if postcondition failed.

It seems this interesting library is too immature to use in production.

Other thoughts

It seems a good assertion library is hard to pick. My favorites are check-types and expect.js. Because the expect.js project is not actively maintained I would use should.js instead. Still, after considering all aspects, I pick check-types.

  • Easy to read, natural syntax check.verify.number(foo, 'foo should be ...')
  • Good api coverage, works on Node and in browser.
  • Assertions that can check collections, including recursive property checks
  • Does not break minification tools
  • Does not cause JSHint warnings
  • Easy to extend with new methods check.verify.foo = function (val, message) { ... }
  • Small.

Update 1

In the end we wrote our own library of lazy assertions - pass as many arguments as needed without paying performance penalty, because the exception message will be formed only if the first argument is falsy. See lazy-ass on github.