Many Dojo developers are aware of the Dojo Objective Harness (DOH) that the Dojo Toolkit uses for unit testing. Many people, however, want to use DOH for testing their own code or even non-Dojo code that they have written. While DOH has always supported this, there currently aren’t many examples of doing so. Let’s see if we can help that out.
Out of the box, DOH has supported custom code since the beginning. Tests can include any custom namespaces or code by easily using standard JS techniques. Coming to an understanding of how custom namespaces work with Dojo and how and when DOH loads tests will help us illustrate how you can take advantage of DOH in your own code.
How do custom namespace lookups work in Dojo?
Dojo’s system for using custom namespaces is quite simple to understand and use. Dojo maps the top level namepaces (ie, ‘dojo’ or ‘company’) relative to the location of the dojo.js
file. The dojo
namespace itself is mapped to the directory where dojo.js
is located. The other major Dojo projects that are distributed with or alongside of the toolkit are typically located at ../module
. Let’s look at a Dojo checkout:
dojotoolkit/
dojo/
dojo.js
dijit/
dojox/
util/
If we wanted to include custom namespaces with the least amount of effort, adding additional modules inside of the dojotoolkit directory above would suffice. Dojo would automatically resolve any mappings to this location. For example if you dojo.require("company.widget.foo")
, Dojo will attempt to load the foo resource from dojotoolkit/dojo/../company/widget/foo.js
. While this is easy, it often means that you would have to intermingle your own code with the Dojo source which can be difficult to upgrade and/or use svn:externals with. We would prefer a structure that doesn’t intermingle code. The common structure is something like this:
js/
dojotoolkit/
dojo/
dijit/
dojox/
util
company/
moduleA/
widget/
Unfortunately, when you dojo.require("company.widget.foo")
, you would get an error because it is looking, by default, in the js/dojotoolkit/dojo/../company
directory and not finding anything except a 404. The solution to this, in normal code, is to use dojo.registerModulePath.
dojo.registerModulePath("company", "../../company");
This solves the location problem, and forces the module’s root to be located at ../../company
relative to the dojo.js
file.
What’s the problem then?
If DOH can support custom code and Dojo provides a mechanism for registering new namespaces, then everything should be ok, right? Close, but not quite. Not only is the code loaded by using the Dojo package system, but so are the tests. Before your test code can be run (where it can use dojo.registerModulePath), the DOH is attempting to load your tests modules using the loader. If your test code is located in js/dojotoolkit/company/
this will be fine because Dojo would automatically look in ../company
for that namepace. As mentioned above, however, that is not ideal, so we put our code in js/company
instead, which of course, Dojo fails to automatically recognize. At the same time, there is no simple way to register those module paths so the custom namespace can be recognized. Tests are launched by naming the tests that you want to run in the URL. If there is no matching resource for a provided test package, it will fail.
How are tests launched?
Typically, there is an HTML file that is simply a redirect to DOH’s runner.html with a single query parameter, testModule, that defines what resource the runner should load. For example, for dojo.rpc, in the dojox/tests/
directory, there is a runTests.html file that serves this purpose. This file can exist anywhere, so long as it redirects to the runner.
Let’s take a look at the redirect:
<meta http-equiv="REFRESH" content="0;
url=../../../util/doh/runner.html?testModule=dojox.rpc.tests.module">
Of course if we’re testing our own code, we want to include our module instead
<meta http-equiv="REFRESH" content="0;
url=../../../util/doh/runner.html?testModule=company.tests.foo">
Without any additional data, runner.html will be unable to locate company.tests.foo to load this resource. To solve this problem, last week I introduced an additional query parameter, registerModulePath, that can be passed to the runner. Additional modules specified here will be registered prior to loading the test files, which allows tests in custom namespaces to work. The above example would be rewritten as:
<meta http-equiv="REFRESH" content="0;
url=../../../util/doh/runner.html?testModule=company.tests.foo
®isterModulePath=company,../../company">
While I don’t believe it is really necessary, you can register multiple namespaces by separating them with a semicolon. (Note: this additional parameter is currently only available in SVN, however for those of you who wish to use this with an older version of Dojo, copying the current DOH code over the old code should present no problems).
What about non-Dojo code?
The above examples are great if you have a Dojo project and you are following the common patterns seen when developing and deploying Dojo. If you want to write tests for your non-Dojo code you can too, though you still have to follow Dojo/DOH semantics when writing the tests themselves. Let’s look at an example project directory structure.
/project
index.html
foo.html
js/
css/
images/
tests/
As you can see this has no relation to how a Dojo project is normally structured. And for this project we don’t have any need for including Dojo in the actual running code, but we’d still like to take advantage of the tests. Here is a simple way to do so without changing directory structure. Include the Dojotoolkit in your tests directory so it looks like:
/project
tests/
dojotoolkit/
dojo/
util/
doh/
Since we don’t want to redo our directory structure to include the tests, we need to map the root of our project relative to dojo.js
as its own module. For example:
<meta http-equiv="REFRESH" content="0;
url=../../../util/doh/runner.html?testModule=project.tests.module
®isterModulePath=project,../../../..">
We then put our test, modules.js
, inside of the tests directory as you would expect. Our runTests.html goes in this same directory as well, so from the top level we don’t really have to follow the typical Dojo pattern. A user or developer of ‘project’ can just go into the tests directory and load runTests.html
.
What’s in the test file itself?
The test file is structured just as any other DOH unit test is and needs to include dojo.provide("project.tests.module")
at the top to satisfy packaging requirements. The typical pattern is to define dojo.require()
s for individual groups of tests in the module.js
file and then the individual tests would be included in those required resources, but this is by no means a necessity. Here is an example module.js done this way:
dojo.provide("project.tests.module");
dojo.require("project.tests.functions");
dojo.require("project.tests.features");
dojo.require("project.tests.errorHandling");
An individual test file can vary drastically in structure, but I would recommend reading the Dojo Book for an example of some of these tests. A simple test might look like this:
dojo.provide("project.tests.functions");
if you are using Dojo already, something like this can include your code
dojo.require("project.someModule");
If not, you might want to include code like this, or you might also want to look at URL based tests below. This is relative to the runner.html file in util/doh.
dojo.io.script.get({
url:"../../../../someModule.js"
});
The tests themselves:
doh.register("project.tests.TestGroupA",
[
{
name: "My Function Test [_myfunc()]",
timeout: 4000,
runTest: function(){
var result = _myFunc("a", "b");
doh.assertEqual("Foo", result);
}
}
]
);
That’s not too bad. You can also create a ‘normal’ HTML page with your code and your tests included. This is especially useful in the non-Dojo case. Inclusion simply uses registerUrl instead of register:
doh.registerUrl("project.tests.TestGroupB",
dojo.moduleUrl("project.tests","testGroupB.html")
);
This will load the file in the project/tests/testGroupB.html
file. Tests can be included in this page for execution with results being reported back to the DOH. You might include something like this on the test page:
<script type="text/javascript" src="runner.js"></script>
<script type="text/javascript">
doh.register("project.tests.TestGroupB", [
function(){
doh.assertEqual("FOO", someObjectOnMyPage);
}
]);
</script>
All in all, tests are pretty easy to write this way once you figure out the pattern. I hope this helps get you started.