Test driven JavaScript done right

Since this summer, I've been test driving all my JavaScript code using Google's fairly fresh JsTestDriver project. If you're intrigued by TDD with JavaScript, including automatic testing (in a console, or - if you're a masochist - an IDE), test suites of 300+ tests running in 498ms as well as shiny red and green output, you should read on.

What is JsTestDriver?

JsTestDriver is seemingly yet another unit testing framework, and it stems from Google. I say seemingly, because the JsTestDriver guys has made some delicious improvements over the normal work flow which sets it apart from other unit testing frameworks for JavaScript so much it's not even funny to compare.

Having said that, JsTestDriver is (as the name implies) really more of a test runner. It ships a default assertion framework, however it doesn't offer all kinds of JavaScript specific assertions. Instead, JsTestDriver makes it fairly accomplish-able to use your current assertion framework of choice with JsTestDriver by providing a bridge. Last time I checked, both QUnit and YUI Test had functional bridges.

JsTestDriver brings innovation to the test runner, not the assertion framework.

The traditional models

JavaScript unit testing frameworks have traditionally been following one of two paths:

  1. Run in the browser. Frameworks like QUnit (jQuery), YUI Test (Yahoo), JsUnitTest and several others rely on a HTML fixture file to load the tests, and a browser to actually run them. Some of these frameworks has managed to take away some of the manual labour by providing Java libraries that start browsers and captures result, but none of the current solutions work as transparently as running a suite of, say Ruby tests.
  2. Run in simulated environment. The other school of JavaScript unit testing - the one that tries to pick the browser out of the loop - use simulation environments, such as Mozilla's Rhino and env.js. An example is the RSpec-like Screw.Unit.

The problem is that while requiring manual interaction with a browser to test generally imposes too much of an overhead, running in a simulated environment is not good enough. Your code is going to run on every fucked up browser out there, and if you're interested in supporting them, you'd better know how your code behaves in them.

How does JsTestDriver work?

JsTestDriver gets the best of two worlds: Your main working area is the console (or the IDE), but your tests actually run in (a) browser(s). JsTestDriver accomplishes this by using three components:

The idea is so simple, yet so incredibly powerful. The browsers poll the server for work to do. When someone runs a test, the browsers execute these and sends results back to the server, which in turn can send them back to whoever started the request. The beauty of this model is:

Note that even though you're not required to provide an HTML fixture file to run the tests, that doesn't mean you can't create some HTML for a test to interact with. The following test case contains a single test that appends some HTML to the body element and makes some assumptions on it. JsTestDriver kindly cleans up the HTML once the test has run.

TestCase("MyHTMLTest", {
    "test should add class name to element": function () {
        /*:DOC += <div id="myDiv"><!-- More content here if you please --></div> */
        var div = document.getElementById("myDiv");
        var className = "jstestdriver";
        myNamespace(div).addClassName(className);

        assertEquals(className, div.className);
    }
});

If you have a lot of tests that require HTML, you can simply stick the HTML creation inside the setUp method.

Speed

Did I mention the speed? I'm currently working on a project that has over 300 tests. It runs in 498ms in Firefox 3.5 (plus the overhead of sending requests, gathering results and so on - usually it all finishes in a second or two). Because the server can push the tests out to any number of browsers concurrently, you can add alot of browsers without increasing the total run time considerably.

Continuous integration

By the way, you can redirect the test output to an XML file, making JsTestDriver easy to include in your continuous integration system. Check out the docs.

What's missing?

I'm used to running autotest for Ruby in a console. JsTestDriver does not currently help out with such a workflow. However, there are plugins for both Eclipse and IntelliJ Idea, if that's your cup of tea.

Anyway, to perfect my own work-flow, I wrote a small Ruby-wrapper to add in the last bits and pieces, such as colored output (red/green), autotest for the console and a `jstestdriver` command (in place of `java -jar ~/bin/java/JsTestDriver.jar`).

Get started!

By now, you should be thinking "my, oh my, now there's no good reason for me not to test my JavaScript. Get me started immediately!". And right you are, get started! It's a simple matter of writing a configuration file that tells JsTestDriver which server to run tests against and which files to load. The usual starting point is four lines of yaml configuration.

If you have Ruby available, do check out my jstdutil gem.

Mad props to the JsTestDriver team for an awesome project!

For those of you keen on the topic, I'm currently writing a book on JavaScript and test driven development for Addison-Wesley. Look for it sometime next year.

Follow me on Twitter.

Published 4. November 2009 in javascript og test driven development.

Possibly related