In my previous post on the Dojo Object Harness (DOH), I discussed how to write unit tests for custom code in custom places. Alex and Dylan both suggested that I write a follow up post describing how to write asynchronous tests. Lets skip the formalities, and get right to it.

The easiest tests (the synchronous ones) are easy to understand. They simply contain one or more functions that exercise a test condition and then (or while) evaluate the results of the exercises to see if they match a an expected value. For example,

tests.register("tests.example", [
     function myTest(t){
            var results = someFunction(a, b);   
            t.assertEqual(results, "myExpectedResult");
     },
     ....more test functions
]);

This is easy enough, but it falls apart when we need to test an I/O action, Animation, or something else that is asynchronous by nature. DOH makes this super easy. Simply return a doh.Deferred object from your test. Let’s look at an example,

tests.register("tests.DeferredExample",[
     function myDeferredTest(t){
           var def = new doh.Deferred();
           return def;
    },
    .... more test functions
]);

This is pretty simple, though obviously useless. DOH will recognize that the returned object is an instance of a doh.Deferred object, and add callbacks and errbacks onto its callback chain analyze the results of the test. However this particular test will fail. It will eventually timeout because nothing ever triggers the Deferred’s callback method. This simple example, made to not fail would look like this:

tests.register("tests.DeferredExample",[
     function myDeferredTest(t){
           var def = new doh.Deferred();
           def.callback(true);
           return def;
    },
    .... more test functions
]);

Simply triggering this callback will make this test pass.

Note that this is a doh.Deferred object, not a dojo.Deferred object. While there are really no differences between the implementations of the two, doh is internal to the testing system and contains no dependencies on Dojo itself. The test runner currently only checks for instances of doh.Deferred, so returning a dojo.Deferred will not work.

A ‘real’ asynchronous request likely returns a dojo.Deferred, so it can’t be used directly. Furthermore, we need to actually perform tests on the data that returns from an async call to verify that it did actually work. The technique that I use to do this is to create a doh.Deferred() at the beginning of my test function, then when I make a call or operation that returns a dojo.Deferred, I add callbacks to it that perform the tests and then ultimately fire the doh.Deferred’s callback method. Discussing Deferreds is often difficult because of the number of times we have to use that word, so lets illustrate with code:

tests.register("tests.DeferredExample",[
     function myDeferredTest(t){
           var dohDeferred = new doh.Deferred();
           var expectedResult = "foo";         

           var realDeferred = someAsyncFunction();

           realDeferred.addBoth(function(result){
                    if (result==expectedResult) {
                       //our test succeeded, fire the callback
                       dohDeferred.callback(true)
                    }else{
                       dohDeferred.errback(new Error("Unexpected Return: ", result));
                    }
           });
              
           return dohDeferred;
    },
    .... more test functions
]);

As you can see, we make a normal asynchronous call, which returns a dojo.Deferred object. To this, we add a function to evaluate the results. Normal assert tests don’t work here as they aren’t associated with the testing properly, so we basically perform the tests and then take the appropriate action against the doh.Deferred object.

Additionally, it is useful to provide a timeout value so that these async operations don’t go on forever or timeout too soon. Here is the same test, represented more completely:

tests.register("tests.DeferredExample",[
     {
          name: "myDeferredTest",
          timeout: 4000,
          runTest: function(t){
              var dohDeferred = new doh.Deferred();
              var expectedResult = "foo";         

              var realDeferred = someAsyncFunction();

              realDeferred.addBoth(function(result){
                    if (result==expectedResult) {
                       //our test succeeded, fire the callback
                       dohDeferred.callback(true)
                    }else{
                       dohDeferred.errback(new Error("Unexpected Return: ", result));
                    }
               });
              
               return dohDeferred;
            }
      },
    .... more test functions
]);

Pretty simple. We used this basic technique when creating tests for the dojo RPC system. If you take a look at some of those tests, such as the Geonames test file and the Yahoo test file, you will see this combined with creating a custom test wrapper so that we don’t have to type quite so much for each test.

DOH is great for writing simple tests, both synchronous and asynchronous, that can be easily automated provided there is something to evaluate to see if a test succeeds. API calls, testing for javascript errors, I/O calls, DOM searches, etc are all easily testable with DOH. DOH’s strengths as a unit test harness do not yet translate to the verification of proper rendering of a page in various browsers. Browser rendering differences, resolutions, view port size, etc are but some of the problem that remain difficult. Utilities such as Windmill and Selenium are both available for these purposes and to meet more complex automated user interface testing needs.