Testing and JavaScript: Useful Libraries


19 November 2012, by

previous article in series

It can be difficult to find useful JavaScript testing-related libraries, to provide lots of the extra functionality that’s typically necessary to make automated testing practical. This is necessarily a very broad topic, but there’s a few key components that almost all testing requires, so I’ve skimmed through some of the options in each.

Mocking

Any real level of unit testing requires mocks, to let you limit the scope of your tests, so you can independently check components of your codebase and their interactions with the rest. Some JavaScript test frameworks include mock functionality, such as Jasmine, but most expect you to bring your own.

Sinon.js

Sinon.js actually covers every categories here, including some simple matching and assert functionality, but it’s essentially a mocking tool. It’s modeled around three tiers of fake objects: spies, which act as fake functions or wrap real functions, and later provide data on how they were called; stubs, which act as spies with pre-programmed behaviour, like returning a result, or throwing an exception; and mocks, which act as stubs with pre-programmed expectations, allowing detailed declarations of expected usage up-front in your tests. An example:

var f = sinon.spy(document, "getElementById");
document.getElementById("id");
assert(f.called);
assert(f.calledWith("id"));

var g = sinon.stub().returns(false);
assertFalse(g());

var h = sinon.mock(jQuery).expects("ajax").thrice();
// ...
h.verify();

Sinon.js includes some cool more javascript-specific features, like a convenient hook to fake out all AJAX requests for stubbing and mocking, an even simpler AJAX mocking solution based around fake server model, a manually controllable time solution similar to Jasmine’s Clock.tick() approach discussed in the previous post, mechanisms for automatic cleanup and verification, and a simple assertion framework.

Finally, Sinon.js has a reasonable selection of adapters to other tools, including Jasmine and QUnit test frameworks (see the previous post), and the Chai assertion framework (see below).

JsMockito

JsMockito will look immediately familiar to anybody that’s ever used Mockito, the popular Java framework on which it’s based, and also uses the matching functionality from JsHamcrest (see below), another Java cross-over. Combine this with Js-Test-Driver and you can get very close your standard Java testing suite.

To my mind, JsMockito’s syntax is slicker and more fluent than Sinon.Js, with the whole framework quite close to actual method calls, and none of the string magic of Sinon.js. Its model is simpler and clearer too; consisting of mocks, providing verification and stubbing, and spies, providing verification and stubbing around an existing object. As with Sinon, it includes mocking functionality for both objects and standalone functions, which is obviously not available in Java, and is a notably more uncomfortable part of the API than the rest.

JsMockito doesn’t have the full feature set of Java Mockito, such as ArgumentCaptors, or any annotation-based functionality, but being outside of Java’s type system does eliminate quite a few restrictions and pain points of the namesake, actually improving on the interface itself and making it significantly more consistent (skipping doThrow, etc), as illustrated below:

var mockArray = mock(Array);
when(mockArray).slice(lessThan(10)).thenReturn('x');
mockArray.slice(5); // == 'x'
verify(mockArray).slice(greaterThan(4));

var mockFunc = mockFunction();
when(mockFunc)('a').thenThrow('Exception');

document = spy(document);
verify(document, never()).getElementById("id");

Although this feels pretty slick and is very powerful, JsMockito is quite minimal and really doesn’t offer much more than this, providing essentially no JavaScript-specific test functionality, like timer control or AJAX helpers. It does however integrate effectively into a very wide variety of common test frameworks (including QUnit, JsTestDriver, YUITest, jSpec, and notably excluding Jasmine, although that does have its own mocking framework anyway).

Matching

JsHamcrest

As far as I can tell, JsHamcrest is actually the only standalone JavaScript matching library available, although many of the other testing components (frameworks, mocking tools etc.) do include their own implementations. It survives in this little space for a couple of reasons: Firsly, it shares an API with its obvious namesake Hamcrest, a popular matching framework for Java, and thereby gives Hamcrest people something familiar in JavaScript-land. Second, by existing as a separate component it becomes practical for use outside of testing entirely, providing a clean, powerful and familiar (maybe) syntax to filter anything, opening possibilities to use it for searching datasets, or validating form inputs.

JsHamcrest has a substantial set of useful core matchers, such as nil(x), empty(list), greaterThan(y), between(a, b), raises(exception), hasMember(m), emailAddress(s) and matches(regex). Many of these will can take matchers as arguments, and there are some matchers designed purely for that purpose, like not(x) and anyOf(x, y, z), opening up options like hasSize(either(lessThan(5)).or(greatherThan(100))). In addition, it’s easy enough to write custom matchers, following the same model as the Java library:

var theAnswerToLifeTheUniverseAndEverything = function() {
return new JsHamcrest.SimpleMatcher({
matches: function(actual) {
return actual == 42;
},
describeTo: function(description) {
description.append('the answer to life, the universe, and everything');
}
});
};

Matchers can then be used by directly calling .matches (e.g. if(lessThan(10).matches(results.length)) … ), or using some of utility methods for this that JsHamcrest includes such as filter(array, matcherOrValue) or assert(value, matcherOrValue). Assert() is entirely test-framework agnostic, and as such it won’t actually throw an exception or do anything if the assert fails, rather counter-intuitively, instead returning a description object you can examine, unless you provide fail or pass callbacks to inform your framework of choice of the result. Alternatively, you can activate one of JsHamcrest’s built in framework integration options (e.g. JsHamcrest.Integration.QUnit()), which will define an assertThat method that wraps assert with the callbacks correctly preconfigured. JsHamcrest includes quite a few integration options, such as QUnit, Jasmine, Rhino (which prints to the Rhino console) and bare web browser (which just opens alerts).

Assertions

Most (if not all) test frameworks include some assertion functionality. Picking your own assertion library though allows you to pick the exact syntax and style to tailor it to the type of testing you’re doing, and typically gives you a far wider suite of assertions to play with, if that’s your thing. While more assertions isn’t definitively better — there’s certainly an argument for maintaining some level of simplicity, for clarity — it does mean you can gain assertions that integrate with the other components of your test, providing clean abstractions for more complex checks, such as mock interaction verification.

Assert.js

Assert.js is a delightfully minimal assertion framework, porting the basic assertion components available server-side in Node.Js to the browser. In practice, this is a very similar set of asserts to QUnit, and if that simple pared-down style appeals to you, but QUnit itself doesn’t, you can pull this in to your alternative framework of choice, and have the best of both worlds.

Assert.js does lack documentation, and all I’ve actually been able to find is the source itself, but fortunately that’s very short and readable (look for the numbered comments), and all of the 12 assertions provided are all simple enough to understand. Some examples:

assert.ok(val);
assert.doesNotThrow(func);
assert.deepEqual(actual, expected)

Chai

Chai meanwhile is vast. It has a large, although not quite totally insane, set of assertions, ranging from .equals and similar basic options, to a selection of numerical tests like .above(x), .within(a, b), etc. and up to .contain.keys(q, r),.match(regex) and .respondTo(‘methodName’). On top of that, it has a selection of plugins that add optional support for integration with a wide variety of other libraries, including integration with Sinon.js and JQuery.

The point where Chai becomes genuinely silly though, is its syntax. It includes not one, not two, but three different syntactic styles (assert/expect/should), and a very impressive fluent API, allowing you to structure assertions pretty much any way you want. Each syntax provides the same basic assertive components, but each under slightly different names, to ensure you can write assertion statements in the most readable possible way. One final set of examples:

var assert = chai.assert;
assert.notInstanceOf(a, X);

var expect = chai.expect;
expect(false).to.not.be.true;

chai.should();
[1, 2, 3].should.have.length.above(2);

I hope this has been helpful, and gives you some ideas for which libraries you’d like to pull in to your JavaScript testing process. Stay tuned for the next in the series, where we’ll discuss how to actually get your automated tests running automatically, out of your browser.

Tags: , ,

Categories: Technical

«
»

Leave a Reply

* Mandatory fields


4 + one =

Submit Comment