Dojorama Part 6: Visual Inspection And Unit Testing

This article is part of the series Dojorama: Building a Dojo Single Page Application

Visual Inspection

Visual inspection tests let us look at any one single UI component in isolation thus eliminating the risk of interference with other components. When creating a visual inspection test, we'll load an HTML page creating one or more instances of the widget under test (such as NavigationWidget). By convention, filenames start with test_, like test_NavigationWidget.html. This page will then load test_NavigationWidget.js containing the actual code:

tests/ui/_global/widget/test_NavigationWidget.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>ui/_global/widget/NavigationWidget</title>
        <link rel="stylesheet" href="/styles/everything.css">
    </head>

    <body>
        <div id="w1"></div>
        <script>
            var dojoConfig = {
                async: 1,
                cacheBust: 1,
                packages: [ // paths are relative to vendor/dojo/dojo/dojo.js
                    { name: 'dojorama', location: '../../..' },
                    { name: 'mijit', location: '../../sirprize/mijit' },
                    { name: 'routed', location: '../../sirprize/routed' }
                ]
            };
        </script>
        <script type="text/javascript" src="/vendor/dojo/dojo/dojo.js"></script>
        <script type="text/javascript" src="test_NavigationWidget.js"></script>
    </body>
</html>

tests/ui/_global/widget/test_NavigationWidget.js

require([
    "routed/Router",
    "routed/Route",
    "dojorama/routing-map"
    'dojorama/ui/_global/widget/NavigationWidget',
    "dojo/domReady!"
], function (
    Router,
    Route,
    map,
    NavigationWidget
) {
    "use strict";

    var router = new Router(),
        name = null,
        cb = function () {},
        w1 = null
    ;

    for (name in map) {
        if (map.hasOwnProperty(name)) {
            router.addRoute(name, new Route(map[name].schema, cb));
        }
    }

    w1 = new NavigationWidget({ router: router }, 'w1');
    w1.startup();
    w1.show();
});

You can see it in action right here

Of course we'll also want to do visual inspection of the full application. In part 2 of this tutorial we've already set things up with tests/app/source/index.html to load our application and the .htaccess file redirecting all requests to this HTML file. To visually inspect the final app source go here or go here for production build inspection.

Unit Testing

Secondly, we want to unit-test our code. The Dojo Toolkit comes with its own fabulous testing framework, named Dojo Objective Harness (DOH). Writing tests with DOH is easy and allows testing of asynchronous actions taking care of test timing, while avoiding overlap issues. Tests can be organized into groups and groups can be registered into modules to be run in sequence by the DOH runner.

Here's an example of two test groups, testing synchronous and asynchronous actions:

tests/model/ReleaseModel.js

require([
    'doh',
    'dojo/store/JsonRest',
    'dojorama/model/ReleaseModel'
], function (
    doh,
    JsonRest,
    ReleaseModel
) {
    doh.register("deserializing an item", [
        {
            name: 'deserialize() should instantiate Date object from formatted string',
            runTest: function () {
                var model = ReleaseModel({
                    props: { releaseDate: null }
                });

                model.deserialize({
                    releaseDate: '2012-12-21'
                });

                doh.t(typeof model.get('releaseDate') === 'object');
                doh.t(model.get('releaseDate').getFullYear() === 2012);
            }
        }
    ]);

    doh.register("updating an item", [
        {
            name: 'callback should be triggered on successful server response',
            timeout: 3000,
            runTest: function () {
                var d = new doh.Deferred(),
                    store = new JsonRest({
                        target: '/api/releases/'
                    }),
                    model = ReleaseModel({
                        store: store
                    }),
                    title = 'Ay Ay Ay';

                model.set({
                    id: 0,
                    title: title
                });

                model.save().then(
                    function (m) {
                        doh.is(m.get('title'), title);
                        d.callback(true);
                    }
                );

                return d;
            }
        }
    ]);

    doh.run();
});

DOH supports various test structures - here we are using the test fixture format which would also let us define setUp() and tearDown() behaviour. DOH proivdes a set of test functions to use inside our unit tests in order to check conditions and report errors if the conditions are not met:

  • doh.assertTrue(boolean)
  • doh.assertFalse(boolean)
  • doh.assertEqual(obj1, obj2)
  • doh.assertNotEqual(obj1, obj2)

In the asynchronous action test above, DOH knows that if a doh.Deferred is returned, then it should pause and wait for either the test timeout to fire or the asynchronous test to resolve, which is done via the d.callback(true) method.

The test will be loaded from an HTML page:

tests/model/ReleaseModel.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>model/ReleaseModel</title>
    </head>

    <body>
        <script>
            var dojoConfig = {
                async: 1,
                cacheBust: 1,
                packages: [ // paths are relative to vendor/dojo/dojo/dojo.js
                    { name: 'dojorama', location: '../../..' },
                    { name: 'mijit', location: '../../sirprize/mijit' },
                    { name: 'routed', location: '../../sirprize/routed' }
                ]
            };
        </script>

        <script type="text/javascript" src="/vendor/dojo/dojo/dojo.js"></script>
        <script type="text/javascript" src="ReleaseModel.js"></script>
    </body>
</html>

... and the HTML page will be registered in a test module:

tests/module.js

define(["require", "doh/main"], function (require, doh) {
    // paths are relative to vendor/dojo/util/doh/runner.html
    doh.register("dojorama/model/ReleaseModel", "../../../../tests/model/ReleaseModel.html", 10000);
    // more tests here
});

Those tests are then run from an HTML page redirecting to the DOH runner passing the location of the test module in the query:

tests/testRunner.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="REFRESH" content="0;url=/vendor/dojo/util/doh/runner.html?test=/tests/module.js">
    </head>

    <body>
        Redirecting to DOH runner.
    </body>
</html>

And here's the DOH runner in action

Conclusion

With this setup we have everything needed to efficiently develop, test and maintain our application. We can run and inspect individual components and we have automated tests to stay confident about the intended functionality of our code over time. We are also able to browse the full application from source or from a production build which is what we are looking at in part 8 of this article series. But first things first and on we go with CSS loading & theming.