Login Register

D.O.H. Unit Testing

Dojo (from 0.9 onward) features a from-scratch unit testing harness, D.O.H., that's both flexible, easy to use, and portable. D.O.H. is designed to run in both browser and command line environments and unlike it's predecessor doesn't need to be run from the build system. D.O.H. has no Dojo dependencies, but can use the Dojo package system if it's available, thereby allowing tests to be run without need of the build tools at all. D.O.H. also includes a browser-based test runner which can execute command-line tests as well as browser-specific tests. When run from the command line, only pure-JS test are run.

Test Registration

D.O.H. defines one global object (doh.*) and code building tests can use APIs defined on it to pass in a test, a group of tests, or a URL to pull a test group from:

doh.registerTest(group, testFuncOrObj);
doh.registerTests(group, testFuncOrObjArr);
doh.registerTestNs(nsObj, objName);
doh.registerTestUrl(url);
doh.register(...);

The tests.register() method accepts the function signatures of any of the other registration functions and determines the correct underlying function to dispatch registration to.

The contents of a typical, command-line-only, test file might look something like:
// file located in core at:
//      tests/moduleToBeTested.js
dojo.provide("tests.moduleToBeTested");
dojo.require("doh.runner");
doh.register("tests.moduleToBeTested",
        [
                // single test, no test fixture
                function assertTrueTest(){
                        doh.assertTrue(true);
                        doh.assertTrue(1);
                        doh.assertTrue(!false);
                },
                // a test fixture
                {
                        name: "thingerTest",
                        setUp: function(){
                                this.thingerToTest = new Thinger();
                                this.thingerToTest.doStuffToInit();
                        },
                        runTest: function(){
                                doh.assertEqual("blah", this.thingerToTest.blahProp);
                                doh.assertFalse(this.thingerToTest.falseProp);
                                // ...
                        },
                        tearDown: function(){
                        }
                },
                // ...
        ]
);

In this example, we see a variant of the test system that uses the doh.addTests() style of add() and registers both independent tests and fixture-driven tests. Note that we give the functions that are registered names even though we could easily provide anonymous functions. This allows the system to more correctly report on what went wrong and where, allowing you to talk more intelligently about your tests passing or failing. The fixture-based example uses the "name" property to provide the same information.

Assertions

D.O.H. exposes a small but adequate number of assertion APIs:

doh.assertEqual(expected, actual); // aliased to: doh.is(e, a)
doh.assertTrue(condition); // aliased to: doh.t(condition)
doh.assertFalse(condition); // aliased to: doh.f(condition)

Asynchronous Tests

D.O.H. provides direct support for asynchronous test cases. Writing asynchronous tests depends on a script context that "knows about" asynchronous execution (aka, a browser but not Rhino) and a slightly modified test authoring syntax:

doh.register("tests.moduleToBeTested",
        // the async  text fixture
        {
                name: "thingerTest",
                timeout: 2000, // 2 seconds, defaults to half a second
                setUp: function(){
                        this.thingerToTest = new Thinger();
                        this.thingerToTest.doStuffToInit();
                },
                runTest: function(){
                        var testCondition = true;
                        var d = new doh.Deferred();
                        setTimeout(function(){
                                try{
                                        if(testCondition){
                                                d.callback(true);
                                        }else{
                                                d.errback(new Error("we got a failure"));
                                        }
                                }catch(e){
                                        d.errback(e);
                                }
                        }, 100);
                        return d;
                }
        }
);

Note that in the above example, the runTest function explicitly returns a doh.Deferred object. This is how the system knows that you are going to be testing potentially asynchronous conditions. Also, in our delayed call (see the setTimeout), we explicitly catch errors and pass them to the Deferred's errback() function. It is expected that your code will do this if you are testing asynchronous conditions. Lastly, you may specify a timeout in milliseconds as part of the fixture object.

As with the previous examples, you can specify an anonymous or single function in place of a the full fixture used here to handle asynchronous cases. The only caveat is that it must return a Deferred object in order to be treated as an async test. We can also simplify the above by using the tests.Deferred classes getTestCallback() method. Here's a simplified async test case:

doh.register("tests.moduleToBeTested", function simplerAsyncTest(){
        var testCondtition = true;
        var d = new doh.Deferred();
        var checkCondition = function(){
                doh.assertTrue(testCondition);
        };
        setTimeout(d.getTestCallback(checkCondition), 100);
        return d;
});

While the test case still needs to pass back a Deferred object, the use of the getTestCallback() to wrap the success or failure test function allows us to stop manually handling exceptions that might be thrown in the callback function, specifically from assertTrue(), assertEqual(), or assertFalse().

Group Registration

Many times, it's advantageous to register an entire group of tests at once. D.O.H. provides a method for doing this as well as for registering group-level setUp and tearDown methods. The tests.registerGroup(name, tests, setUp, tearDown) method lets you handle this in a single call:

// file located in core at:
//      tests/fullGroupTest.js
dojo.provide("tests.fullGroupTest");
dojo.require("tests.runner");
doh.registerGroup("tests.fullGroupTest",
        [
                // single test, no test fixture
                function assertTrueTest(t){ t.t(true); },
                // string variant of the same:
                "doh.t(true);",
                // test that uses variable set up by group
                function assertTrueTest(t){
                        t.t(tests.fullGroupTest._localVariable);
                },
                // ...
        ],
        function(){ // setUp
                tests.fullGroupTest._localVariable = true;
        },
        function(){ // tearDown
                tests.fullGroupTest._localVariable = false;
        }
);

Note that when using registerGroup, setUp and tearDown replace existing group-level handlers, but the registered tests are additive to any pre-existing tests registered for the group.

The above example also introduces yet another shorthand for writing tests, the string-only test. This style of test authoring is particularly terse. Tests written this way do not provide explicit fixture names and so the test code itself is used as the test name in reporting. In these tests, there is also always a variable t which is an alias to the global tests variable. This allows for very compact tests to be written in the form:

[
        "doh.t(true);",
        "doh.f(!true);",
        "doh.is('thinger', 'thing'+'er')",
        // ...
]

With group registration, this style of test authoring requires very little typing. Just mind your string quotes!

URL-based Testing

Being developed explicitly to test JavaScript applications, D.O.H. includes features for browser-based test harnesses to load sub-documents which may run a set of tests explicitly on a browser-provided DOM. This lets you automate UI testing and isolate browser-specific bugs by writing tests once and quickly running them through the unified test harness UI. To support this, browser runtimes for D.O.H. provide an implementation for tests.registerUrl(groupName, url). On other environments, this may be a no-op.

A real example from Dojo Core:

doh.registerUrl("tests._base.NodeList", dojo.moduleUrl("tests", "_base/NodeList.html"));

This example uses Dojo to normalize the tested URL with relationship to the loading code, but you can just as easily specify a full URL manually. Just be aware that in order for D.O.H. to be able to record the results of tests from this page, it must be hosted on the same domain as the hosting test harness.

But what does the page itself look like? Here's a snapshot of the page referenced above:

<html>
        <head>
                <title>testing dojo.NodeList</title>
                <script type="text/javascript" src="../../dojo.js"
                        djConfig="isDebug: true">
</script>
                <script type="text/javascript">
                        <b>dojo.require("doh.runner");</b>
                        dojo.addOnLoad(function(){
                                doh.register("t",
                                        [
                                                function ctor(){
                                                        var nl = new dojo.NodeList();
                                                        nl.push(dojo.byId("c1"));
                                                        doh.assertEqual(1, nl.length);
                                                },
                                                // ...
                                        ]
                                );
                                <b>doh.run();</b>
                        });
                </script>
        </head>
        <body>
                <h1>testing dojo.NodeList</h1>
                <div id="t">
                        <span id="c1">c1</span>
                </div>
        </body>
</html>

The above code has several important features (in bold). First, we ensure that the test system itself is loaded into the tested page. Without this, the tests won't run. D.O.H. is smart enough to know if it's being loaded into a child frame or as a parent document. If you load this file into a normal browser window, the tests will still run, but you won't get the pretty D.O.H. chrome or audio feedback. Instead, the results of only the tests from this page will be sent to whatever console facility is available.

The second important feature of our tested URL is that it manually calls tests.run(), in this case after the page has been loaded and tests have been registered (a good time to do it). There are Dojo-isms in the test page, but they don't affect the important bits of the system. You can still load the test system with <script> tags and hard-wired URLs and this file would participate in the larger test group correctly.

Since testing on loaded pages may take a long time (relatively), a default timeout of 10 seconds per URL is provided. If your tested page requires more (or less) time, you can pass an explicit timeout parameter to the tests.registerUrl method:

doh.registerUrl(
        "tests._base.NodeList",
        dojo.moduleUrl("tests", "_base/NodeList.html"),
        5000); // 5000ms, or 5 seconds

Running the tests in Rhino

Here are some instructions for running the tests in Rhino. Ideally they should run in any Rhino version 1.6R4 or later, however it has only be verified to work with the custom_rhino.jar in Dojo's util repository (it is in util/buildscripts/lib). Steps:

> svn svn co http://svn.dojotoolkit.org/dojo/view/anon/all/trunk dojo_0.9
> cd dojo_0.9
> cd util/doh
> java -jar ../buildscripts/lib/custom_rhino.jar runner.js

NOTE:Do not use a built version of dojo when trying to run DOH through the Rhino command line. The built versions of Dojo that are available for download are optimized for use in the browser, and they do not have the auto-detection logic to load the right environment code for Rhino. So be sure to use a source distribution when running the DOH tests in Rhino.

Including Tests From Custom Modules

FIXME: TODOC

Including Tests Without Dojo

FIXME: TODOC

test fixture in URL-based Testing context

After some test, I think than syntax below don't work in URL-based Testing context.

                // a test fixture
                {
                        name: "thingerTest",
                        setUp: function(){
                                this.thingerToTest = new Thinger();
                                this.thingerToTest.doStuffToInit();
                        },
                        runTest: function(){
                                doh.assertEqual("blah", this.thingerToTest.blahProp);
                                doh.assertFalse(this.thingerToTest.falseProp);
                                // ...
                        },
                        tearDown: function(){
                        }
                },
                // ...

Assert Functions

It would be nice if the assertTrue, assertFalse, and assertEquals functions would accept a string message argument. I'm reading through the code, and it doesn't appear that they do.

David S. Boyer
mangr3n at gmail (figure the rest out on your own )

The "Running the tests in Rhino" section is slightly incorrect

The "Running the tests in Rhino" section should contain :

java -jar ../shrinksafe/custom_rhino.jar runner.js

One other thing to mention, is the arguments that runner.js parses :

dojoUrl=/path/to/dojo.js

* load this dojo.js file instead of the default one

testUrl=/path/to/test.js

* load this javascript file after dojo.js

testModule=test.module1,test.module2

* dojo.require each test module in this comma delimited list

for example

java -jar ../shrinksafe/custom_rhino.jar runner.js testModule=dojo.tests.date

Its also worth pointing out

Its also worth pointing out that runner.js should dojo.provide('doh.runner') after it loads dojo.js.

runner.js does do dojo.provide('doh.runner') at the start, but when running from the command line, dojo isn't yet loaded.

Without this, in my custom test, calls to dojo.require('doj.runner') fail

bug in doh

when using doh, there's a bug in runner.js.

doh.registerTestNs = function(/*String*/ group, /*Object*/ ns)

line 331:

if( (x.charAt(0) == "_") &&

should be changed to

if( (x.charAt(0) != "_") &&

Re: bug in doh

Is there any other tutorial material

I am afraid that this article just assumes too much. How do I bootstrap a test system for my application? Is DOH even intended for such an application? Is it just a widget test framework?

For example the line here that says...

'If you load this file into a normal browser window, the tests will still run, but you won't get the pretty D.O.H. chrome or audio feedback'

OK so how do I get that? Is there a test runner somewhere?

Any help would be appreciated.
Paul B.

I second that motion

This article seems to assume some basic knowledge of the DOH - in fact it does not even state what DOH stands for. Is this because there is some other tutorial/information out there for first-time users of this test framework? If so please provide a reference - thank you.

More help please!

"browser-based test runner which can execute command-line tests as well as browser-specific tests. When run from the command line, only pure-JS test are run"

Pls provide more info on how to use this runner. I see the file runner.html - but how do I use it? It seems to take parameters like dojoUrl, testUrl, etc. Any guidance on how to use these?

Any information is appreciated.

Thanks.

The tests variable

The tests variable is mentioned the it is not explained and not used in the subsequent example. Can you please clarify whether doh == tests (it seems to work in the code) and why there is yet another alias t for it - so

t== tests == doh (all global variables all the same).

True?

-A.