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.
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
Additionally, note that the runner.js file accepts any of the options shown in Table 16-3.
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.provide
s 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.