Adding QUnit to an existing project

We recently decided to use QUnit on a project in order to provide JavaScript unit testing. The project made heavy use of jQuery, so QUnit seemed like an obvious choice that would be easy to integrate into the existing codebase.

The challenge

Under normal circumstances, following the official guide for setting up QUnit would have been enough, but there were a couple of issues we had to overcome:

1. We’re using the AMD format for our JavaScript modules.

2. We want our tests to run automatically as part of our Travis builds.

The AMD format

Our project uses the AMD format to define small modules of code and we use RequireJS to manage and load them.

In order for a QUnit test suite to test a module, it must be able to access that module first, so we had to define our suites as AMD modules themselves and have our modules defined as dependencies for the relevant tests:

define(['path/to/module'], function(module) {
    QUnit.test('Testing something', function(assert) {
        // Make assertions about module
    });
});

For these tests to run we request the files using RequireJS instead of directly loading them via script tags:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>QUnit Unit tests</title>
    <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.14.0.css">
</head>
<body>
 
    <!-- Element for holding QUnit contents -->
    <div id="qunit"></div>
    <!-- Element for holding QUnit fixtures -->
    <div id="qunit-fixture"></div>
 
    <!-- Download QUnit -->
    <script src="http://code.jquery.com/qunit/qunit-1.14.0.js"></script>
 
    <!-- Prevent QUnit from starting automatically. -->
    <!-- Instead we want to start it through RequireJS -->
 
    <script type="text/javascript" charset="utf-8">
        QUnit.config.autostart = false;
    </script>
 
    <!-- Load Require JS and point it at a starting point file (main.js) -->
    <script data-main="main" src="path/to/require.js"></script>
</body>
</html>

The main.js file referenced in the data-main attribute for RequireJS looks something like this:

/*global require*/
(function() {
    'use strict';
 
    // The set of test files.
    var tests = [
        'path/to/tests/for/myModule'
        // Add more test suites as needed.
    ];
 
    // Define paths to vendor dependencies and other bundles as necessary.
    require.config({
        baseUrl: '',
        // Some commonly used paths in our modules
        paths: {
            "domReady": 'path/to/vendor/domReady',
            "jquery": 'path/to/vendor/jQuery',
            "bootstrap": 'path/to/vendor/bootstrap',
            "modules": 'path/to/my/modules'
        }
    });
 
    // Require the unit tests dependencies and run them as required.
    require(tests, function() {
        QUnit.start();
    });
})();

So now when we view the page in a browser, RequireJS will load all of our test modules (which in turn will pull in the modules they need to test as dependencies) before triggering QUnit to start testing.

We can see our simple example runs and all the tests pass:

QUnit tests results page

Reporting results in Travis

We already use Travis to automatically run tests and code style checks against our JavaScript and PHP, and now we want to add our QUnit tests to that too.

We can use PhantomJS to run the test file as part of our Travis build, but importantly, if any of the tests fail we need the build as a whole to fail so that Travis can report back to GitHub before any code gets merged. To do this, the file running our QUnit tests needs to exit with a 1 (fail) or 0 (pass) code after completing the test suites.

So instead of directly running the HTML file containing the tests, we instead run a JavaScript file designed to watch for test suites finishing, and report back to Travis when everything is done.

Add the following to your travis.yml file:

script:
    phantomjs tests.js path/to/tests.html

So we run a tests.js file instead, and pass the location of the tests as an argument to the script. Inside of tests.js we can execute that HTML file and watch for the QUnit events finishing in order to output the results.

The below code shows a cut-down version of the tests.js file which will pass or fail a Travis build based on the success of a QUnit test suite:

(function() {
    'use strict';
 
    var page = require('webpage').create(),
        args = require('system').args,
        url = args[1];
 
    /**
     * Evaluate the page once initialised using the watch function.
     */
    page.onInitialized = function() {
        page.evaluate(watch);
    };
 
    /**
     * Whenever the page receives a callback, check if QUnit
     * is finished, and exit with the appropriate status.
     *
     * 1 === failed
     * 0 === passed
     */
    page.onCallback = function(message) {
 
        // Multiple callbacks can be passed to the page, but the only
        // one which should cause an exit is when the QUnit.done event
        // is fired.
 
        if (message && message.name === 'QUnit.done') {
            // The build will fail if there is no data passed to us by QUnit
            // or if there was at least one failing test.
            var failed = !message.data || message.data.failed;
            phantom.exit(failed ? 1 : 0);
        }
    };
 
    /**
     * Open a page at the url specified. The onInitialized event defined
     * above will be triggered automatically in order to evaluate the page.
     */
    page.open(url, function(status) {
        // If the page failed to open, exit as a failing build.
        if (status !== 'success') {
            phantom.exit(1);
        }
 
         // Nothing else needs doing because the onInitialized event
         // will now fire and trigger the defined callback above.
 
    });
 
    /**
     * Evaluates a given page, listening for the QUnit.done event.
     *
     * It's possible to listen to many more events than just the QUnit.done
     * event, such as logging, and individual tests finishing. You could then
     * log these events to the page, and have PhantomJS output the text to
     * Travis.
     */
    function watch() {
        window.document.addEventListener('DOMContentLoaded', function() {
            // Once QUnit is finished, we trigger a callback to the page and
            // pass the results of the tests to it for evaluation.
            QUnit.done(function(result) {
                window.callPhantom({
                    'name': 'QUnit.done',
                    'data': result
                });
            });
        }, false);
    };
})();

So with all this in place, we can add test suites as needed and they are automatically picked up and run as part of the Travis build for this project, while continuing to let us use the AMD format for our Javascript.

At Box UK we have a strong team of bespoke software consultants with more than two decades of bespoke software development experience. If you’re interested in finding out more about how we can help you, contact us on +44 (0)20 7439 1900 or email info@boxuk.com.

About the Author

Tom Houdmont

For more than ten years Tom has worked on creating scalable solutions for projects of all shapes, sizes and goals. He has extensive experience working with changing requirements and rapidly iterating ideas and implementations to produce the most appropriate results in the shortest possible time.