Eric Radman : a Journal

Testing JavaScript with HTML Fixtures

To meaningfully test JavaScript that is part of a web application one needs to interact with the browser's DOM. Most large and small JS test frameworks approach this problem by creating and document and then adding the tests to the document

<!doctype html>
<html>
  <head><meta charset="utf-8"></head>
  <body>
  <!-- insert HTML and SVG here -->
  </body>
  <script src="testrunner.js"></script>
  <script src="mycode.js"></script>
</html>
</code>

The obvious problem is that this cannot be easily included in automated tests since a manual browser load and refresh is required.

The second difficulty is that each individual tests require careful setup and teardown if they modify the document they're embedded in. If you are testing client code for a web application it almost certainly does modify the DOM state, which results in interacting unit tests.

Fresh Fixtures in Node.js

For the command line, node is an obvious place to start because it has good exception handling (if you can put up with the long backtraces) and a rich library. Now instead of writing a document with embedded tests we can start writing tests that interact with data (the document)

var assert = require('assert');

The downside to starting with Node is that there is no HTML, CSS or SVG. No DOM. To solve this problem we can use jsdom, a library with nearly 90 dependencies. Once installed we can construct a document

var jsdom = require("jsdom").jsdom;

Immedately we are faced with the sad fact that JavaScript does not have a native syntax of writing blocks of litteral text. Alternatively we could load the string from a file. Now we'll include the library we want to test

var app = require('../public/ui.js');

We still have to choose a test runner. The following example uses Mocha. The important part is that we get a new jsdom object instantiated before each test, so careful setup or teardown is not required

describe("app", function() {
    var window = null;

    beforeEach(function(){
        window = jsdom(html).defaultView;
    });

    describe("test 1", function() {
        it("contains an embeded product ID", function () {
            content_div = window.document.getElementById('content')[0];
            assert.equal(chart_div.innerHTML, "...");
        });
    });
});

Native Browser Tests with phantom-assert

At the time of this writing jsdom no longer supports node.js and requires io.js instead. If this is or a lengthy chain of NPM modules is not an option in your environment then it is possible to write automated tests using a test runner for phantomjs. The project I crated to prove this concept is phantom-assert, and works by injecting each test function into the page and resetting the page content inbetween each test. A simple example looks like this

var html = "fixture.html";
var tests = [];

tests.push(function count_chart_elements () {
    assert(document.getElementsByClassName('chart').length, 1);
})

Using this model is impossible to create interacting tests because the page content is refreshed after each test. phantom-assert is not a micro-framework, it is a working example who's source code can be read in less than 5 minutes. This level of minimalism eliminates the need for configuration because the framework itself can be easily modified and embedded directly into your project.

Last updated on December 31, 2018