Dojo Objective Harness (DOH)

Automated testing practices for web applications are becoming increasingly common because of the sheer amount of coding and complexity involved in many of today's rich Internet applications. DOH uses Dojo internally but is not a Dojo-specific tool; like ShrinkSafe, you could use it to create unit tests for any JavaScript scripts, although no DOM manipulation or browser-specific functions will be available.

DOH provides three simple assertion constructs that go a long way toward automating your tests. Each of these assertions is provided via the global object, doh, exposed by the framework:

  • doh.assertEqual(expected, actual)

  • doh.assertTrue(condition)

  • doh.assertFalse(condition)

Before diving into some of the more complex things that you can do with DOH, take a look at trivial test harness that you can run from the command line via Rhino to get a better idea of exactly the kinds of things you could be doing with DOH. The harness below demonstrates the ability for DOH to run standalone tests via regular Function objects as well as via test fixtures. Test fixtures are little more than a way of surrounding a test with initialization and clean up.

Rhino Test Harness Without Dojo

Without further ado, here's that test harness. Note that the harness doesn't involve any Dojo specifics; it merely uses the doh object. In particular, the doh.register function is used in this example, where the first parameter specifies a module name (a JavaScript file located as a sibling of the util directory), and the second parameter provides a list of test functions and fixtures:

doh.register("testMe", [

    //test fixture that passes
    {
        name : "fooTest",
        setUp : function(  ) {},
        runTest : function(t) { t.assertTrue(1); },
        tearDown : function(  ) {}
    },
    //test fixture that fails
    {
        name : "barTest",
        setUp : function(  ) { this.bar="bar"},
        runTest : function(t) { t.assertEqual(this.bar, "b"+"a"+"rr"
); },
        tearDown : function(  ) {delete this.bar;}
    },
    //standalone function that passes
    function baz(  ) {doh.assertFalse(0)}

]);

Assuming this test harness were saved in a testMe.js file and placed alongside the util directory, you could run it by executing the following command from within util/doh. (Note that although the custom Rhino jar included with the build tools is used, any recent Rhino jar should work just fine):

java -jar ../shrinksafe/custom_rhino.jar runner.js dojoUrl="../../dojo/dojo.js"
testModule=testMe

The command simply tells the Rhino jar to run the testMe module via the runner.js JavaScript file (the substance of DOH) using the copy of Base specified. Although no Dojo was involved in the test harness itself, DOH does use Base internally, so you do have to provide a path to it.

Now that you've seen DOH in action, you're ready for Table 16-2, which summarizes the additional functions exposed by the doh object.

Table 16-2. doh module functions

Function

Comment

registerTest(/*String*/group,

/* Function || Object */ test)

Adds the test or fixture object to the specified test group.

registerTests(/*String*/group,

/*Array*/ tests)

Automates registering a group of tests provided in the tests Array.

registerTestNs(/*String*/group,

/*Object*/ns)

Adds the functions included in the ns object to the collection that should be test group. Functions beginning with an underscore are not included since the underscore normally denotes the notion of private.

register(/* ...*/)

Applies the proper register function by inspecting the arguments and determining which one to use.

assertEqual(expected, actual)

Used to assert that two values should be equal.

assertTrue(/*Boolean*/condition)

Used to assert that a value should evaluate to true.

assertFalse(/*Boolean*/ condition)

Used to assert that a value should evaluate to false.

is(expected, actual)

Shorthand for assertEqual.

t(/*Boolean*/condition)

Shorthand for assertTrue.

f(/*Boolean*/condition)

Shorthand for assertFalse.

registerGroup(/*String*/ group,

/*Array||Function||Object*/tests, /*Function*/ setUp,

/*Function*/tearDown)

Adds an entire group of tests provided in tests to the group at one time. Uses a custom setUp and tearDown function, if provided.

run( )

Used to programmatically run tests.

runGroup(/*String*/groupName)

Used to programmatically run a group of tests.

pause

Can be used to programmatically pause tests that are running; they may be resumed with run( ).

togglePaused

May be applied sequentially to pause and run the tests.

Additionally, note that the runner.js file accepts any of the options shown in Table 16-3.

Table 16-3. Options for runner.js

Function

Comment

dojoUrl

The path to dojo.js.

testUrl

The path to a test file.

testModule

A comma-separated list of test modules that should be executed, such as foo.bar, foo.baz.

Rhino Test Harness with Dojo

Although it is possible to use DOH without Dojo, chances are that you will want to use Dojo with Rhino. Core contains some great examples that you can run by executing runner.js without any additional arguments. The default values will point to the tests located in dojo/tests and use the version of Base located at dojo/dojo.js.

If you peek inside any of Core's test files, you'll see the usage is straightforward enough. Each file begins with a dojo.provide that specifies the name of the test module, requires the resources that are being tested, and then uses a series of register functions to create fixtures for the tests.

Assume you have a custom foo.bar module located at /tmp/foo/bar.js and that you have a testBar.js test harness located at /tmp/testBar.js. The contents of each JavaScript file follows.

First, there's testBar.js:

/* dojo.provide the test module just like any other module */
dojo.provide("testBar");

/* You may need to register your module paths when using
   custom modules outside of the dojo root directory */
dojo.registerModulePath("foo.bar", "/tmp/foo/bar");

/* dojo.require anything you might need */
dojo.require("foo.bar");

/* register the module */
doh.register("testBar", [

    function(  ) { doh.t(alwaysReturnsTrue(  )); },
    function(  ) { doh.f(alwaysReturnsFalse(  )); },
    function(  ) { doh.is(alwaysReturnsOdd(  )%2, 1); },
    function(  ) { doh.is(alwaysReturnsOdd(  )%2, 1); },
    function(  ) { doh.is(alwaysReturnsOdd(  )%2, 1); },
    {
        name : "BazFixture",
        setUp : function(  ) {this.baz = new Baz;},
        runTest : function(  ) {doh.is(this.baz.talk(  ), "hello");},
        tearDown : function(  ) {delete this.baz;}
    }
]);

And now, for your foo.bar module residing in foo/bar.js:

/* A collection of not-so-useful functions */
dojo.provide("foo.bar");

function alwaysReturnsTrue(  ) {
    return true;
}

function alwaysReturnsFalse(  ) {
    return false;
}

function alwaysReturnsOdd(  ) {
    return Math.floor(Math.random(  )*10)*2-1;
}

// Look, there's even a "class"
dojo.declare("Baz", null, {
    talk : function(  ) {
        return "hello";
    }
});

The following command from within util/buildscripts kicks off the tests:

java -jar ../shrinksafe/custom_rhino.jar runner.js dojoUrl=../../dojo/dojo.js
testUrl=/tmp/testBar.js

Warning

Especially note that the test harness explicitly registered the module path for foo.bar before requiring it. For resources outside of the dojo root directory, this extra step is necessary for locating your custom module.

If all goes as planned, you'd see a test summary message indicating that all tests passed or failed. Registering a group of tests sharing some common setup and tear down criteria entails the very same approach, except you would use the doh.registerGroup function instead of the doh.register function (or a more specific variation thereof).

If you want more finely grained control over the execution of your tests so you can pause and restart them programmatically, you apply the following updates to testBar.js:

/* load up dojo.js and runner.js */
load("/usr/local/dojo/dojo.js");
load("/usr/local/dojo/util/doh/runner.js");

/* dojo.provide the test module just like any other module */
dojo.provide("testBar");
/* You may need to register your module paths when using
   custom modules outside of the dojo root directory */
dojo.registerModulePath("foo.bar", "/tmp/foo/bar");

/* dojo.require anything you might need */
dojo.require("foo.bar");

/* register the module */
doh.register("testBar", [

    function(  ) { doh.t(alwaysReturnsTrue(  )); },
    function(  ) { doh.f(alwaysReturnsFalse(  )); },
    function(  ) { doh.is(alwaysReturnsOdd(  )%2, 1); },
    function(  ) { doh.is(alwaysReturnsOdd(  )%2, 1); },
    function(  ) { doh.is(alwaysReturnsOdd(  )%2, 1); },
    {
        name : "BazFixture",
        setUp : function(  ) {this.baz = new Baz;},
        runTest : function(  ) {doh.is(this.baz.talk(  ), "hello");},
        tearDown : function(  ) {delete this.baz;}
    }
]);

doh.run(  );
/* pause and restart at will... */

Although we didn't make use of the fact that testBar is a module that dojo.provides itself, you can very easily aggregate collections of tests together via dojo.require, just like you would for any module that provides itself.

Although you could run asynchronous tests using Rhino as well, the next section introduces asynchronous tests because they are particularly useful for browser-based tests involving network input/output and events such as animations.

Get Dojo: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.