To my knowledge, DOH is the only javascript unit testing framework that completely supports browser based AMD testing without any additional plugins or code hacks. Another framework called Buster.js is in beta but listed as unstable at the time. It does appear to offer some good features when complete, including AMD support.

This article focuses on some things I noted while browsing through the DOH code and playing around with the framework.

Note: I read somewhere that DOH was a stand-alone javascript tester. The DOH that ships with the Dojo Toolkit SDK definitely relies on the existence of dojo and dojox. 

Tests

doh.assertEqual - asserts that the two particular inputs are equal to each other

  • Aliased to doh.is
  • Accepts 4 parameters
    • (Object) expected (required) - the expected value
    • (Object) actual (required) - the actual value
    • (String) hint (optional) - additional message if the test fails
    • (Boolean) doNotThrow (optional) - do not throw an assert failure if the test fails

doh.assertNotEqual - asserts that the two particular inputs are not equal to each other

  • Aliased to doh.isNot
  • Accepts 3 parameters
    • (Object) expected (required) - the expected value
    • (Object) actual (required) - the actual value
    • (String) hint (optional) - additional message if the test fails

doh.assertTrue - asserts that a particular condition is true

  • Aliased to doh.t
  • Accepts 2 parameters
    • (Object) condition (required) - the condition expected to be true
    • (String) hint (optional) - additional message if the test fails

doh.assertFalse - asserts that a particular condition is false

  • Aliased to doh.f
  • Accepts 2 parameters
    • (Object) condition (required) - the condition expected to be false
    • (String) hint (optional) - additional message if the test fails

doh.assertError - asserts that a particular error will be thrown by a given function

  • Aliased to doh.e
  • Accepts 5 parameters
    • (Object) error (required) - the error to be thrown
    • (Object) scope (required) - the scope of the function to test
    • (Function) function (required) - the function to test
    • (Array) args (required) - the arguments for the function to test
    • (String) hint (optional) - additional message if the test fails

Custom Matchers

Many other unit test frameworks ship with a number of matchers for performing unit tests. DOH only ships with only the 5 mentioned above. DOH gives you the ability to add more tests a number of ways. One means of doing this is by creating an AMD module with a variety of tests that return true or false and passing these results to the doh.assertTrue test. Another (preferred) means is by using dohPlugins.

dohPlugins provides the means of adding functionality to doh prior to executing the tests. The tests can then be coded with the usual syntax but using the custom tests instead of the built-in tests. Below is a sample plugin module that is loaded as a plugin. It provides doh with a new test: doh.assertIsUndefined.

define(["doh/runner"], function(doh) {
    doh.isUndefined = doh.assertIsUndefined = function(/*Object*/ condition, /*String?*/ hint) {
        // summary: is the passed item undefined?
        if(arguments.length < 1){
            throw new doh._AssertFailure("assertIsUndefined failed because it was not passed at least 1 argument");
        }
        if(typeof condition !== 'undefined'){
            throw new doh._AssertFailure("assertIsUndefined('" + condition + "') failed", hint);
        }
    };
});

The plugin can be loaded with the following url:  path-to-doh/runner.html?dohPlugins=/path-to-lib/doh-matchers.js

...and used with the following test

define(["doh/runner"], function(doh) {
    doh.register("Custom DOH Matchers", [
        function assertUndefinedTest() {
            var myArr = [];
            doh.assertIsUndefined(myArr[0]);
        }
    ]);
});

URL Arguments

  • test- list of modules and/or urls to load
    • If the test involves loading dependent modules, the AMD module format should be used. DOH will perform the AMD before it starts running the tests. An example link would be: path-to-doh/runner.html?test=doh/amdTest
    • If the test is a simple javascript file with no dependencies, the url format should be used. DOH will load the javascript file and start running. An example link would be: path-to-doh/runner.html?test=/my/path/test.js
    • For multiple locations, each location should be separated by a comma.
    • If no test parameter is passed, DOH will load the default module at dojo/tests/module
  • boot- lists the locations to a configuration file and/or AMD loader
    • DOH can load a configuration file which contains standard configuration settings needed by AMD loaders.
    • DOH can also load AMD loaders other than the dojo loader. This makes it very easy to fit DOH into any project that uses AMD and other loaders such as require.js or curl.js.
    • For multiple locations, each location should be separated by a comma. An example link would be: path-to-doh/runner.html?boot=path-to/config.js,path-to/require.js
    • If no boot is specified, DOH attempts to use the default dojo.js loader. It looks for the dojo.js loader to exist in the standard dojo file structure where the dojo and util folders are siblings. dojo.js should be directly under the dojo folder and the doh folder is directly under the util folder.
  • paths- list of path locations to pass to the AMD loader
    • The AMD loader assumes the tests to load are relative to it's location and attempts to load them from there. Often this isn't the case and the paths to the tests should be specified. This can be done using this parameter or in a boot configuration file.
    • For multiple paths, each path setting should be separated by a semicolon. Each path setting should consist of a comma separated from-path and to-path. An example link would be: path-to-doh/runner.html?paths=myui,path-to/libs/myui;jquery,path-to/vendor/jquery
    • As stated previously, the path can be specified in a boot configuration as well. The path information in the configuration file should be defined according the loader in use.
  • breakOnError- tells DOH to call the debugger upon a test failure
    • DOH will continue to run all tests even if one fails. Passing this argument will force DOH to stop running when a test fails. This can useful when trying to figure exactly where and why a test has failed.
    • This is false by default. The value passed with the parameter isn't checked; only it's presence. Any value can be passed but it would be best to pass a truthy value such as true or 1.
  • sandbox- tells DOH to sandbox the dojo and dojox objects it uses
    • This is false by default. The value passed with the parameter isn't checked; only it's presence. Any value can be passed but it would be best to pass a truthy value such as true or 1.
  • async- tells the loader to use async mode when loading scripts
    • This is false by default. The value passed with the parameter isn't checked; only it's presence. Any value can be passed but it would be best to pass a truthy value such as true or 1.
  • dohPlugins- list of file to load before the tests
    • Plugins provide the ability to override aspects of DOH for reporting purposes
    • Multiple plugins can be specified using a comma

Testing Setup

The following is a setup for a basic javascript unit test. The library is called 'simple'. Two scenarios are provided: one with the library in a folder is a sibling of the dojo src folders and one where the library and unit tests exist in a folder thats not a sibling of the dojo src folders.

The first unit test just tests each of the DOH test methods. It is a basic javascript test with no dependencies. The code for the first test is as follows:

define(["doh/runner"], function(doh) {
    doh.register("DOH Methods", [
        function assertTrueTest() {
            doh.assertTrue(true);
            doh.assertFalse(false);
            doh.assertEqual("Kevin", String('Kevin'));
            doh.assertNotEqual("Kevin", String('Kevin A'));
        }
    ]);
});

The urls for loading the above unit test are:

(Case 1): http://localhost/jslibs/dojo/src/1.8.0/util/doh/runner.html?test=/jslibs/simple/simple.js

(Case 2): http://localhost/jslibs/dojo/src/1.8.0/util/doh/runner.html?test=../../simple/simple.js

The second unit test tests a very basic Dojo class with inheritance. The code for this test is as follows:

Person class

define(["dojo/_base/declare"], function(declare){
    return declare([], {
        constructor: function(firstname, lastname){
            this.firstname = firstname;
            this.lastname = lastname;
        },
        fullname: function(){
            return this.firstname + " " + this.lastname;
        },
        speak: function(){
            return this.firstname + " says hello!";
        }
    });
});


Employee class

define(["dojo/_base/declare", "simple/amd/Person"], function(declare, Person){
    return declare([Person], {
        constructor: function(firstname, lastname, title){
            this.inherited(firstname, lastname);
            this.title = title;
        },
        speak: function(){
            return "(" + this.title + ") " + this.firstname + " says hello!";
        }
    });
});


Unit test

define(["doh/runner", "simple/amd/Employee"], function(doh, Employee) {
    doh.register("My Employee Test", {
        "should return the employee's name":{
            setUp:function () {
                this.employee = new Employee("Kevin", "Armstrong", "Developer");
            },
            runTest: function(){
                doh.assertEqual("Kevin", this.employee.firstname);
                doh.assertEqual("Armstrong", this.employee.lastname);
                doh.assertEqual("Kevin Armstrong", this.employee.fullname(), "Fullname should be 'firstname lastname'");
            }
        },
        "should return what the employee said":{
            setUp:function () {
                this.employee = new Employee("John", "Doe", "President");
            },
            runTest: function(){
                doh.assertEqual("(President) John says hello!", this.employee.speak());
            }
        }
    });
});

The urls for loading the above unit test are:

(Case 1): http://localhost/jslibs/dojo/src/1.8.0/util/doh/runner.html?test=simple/amd/employeetest&paths=simple,/jslibs/simple

(Case 2): http://localhost/jslibs/dojo/src/1.8.0/util/doh/runner.html?test=simple/amd/employeetest

This should provide a good start for setting up some unit tests with DOH. The next part will cover using DOH with other loaders such as require.js and curl.js.