Testing asynchronous functions with scriptaculous' unittest.js
unittest.js is kind of rudimentary, but nonetheless handy. The biggest problem I've run into is its shoddy support for asynchronous testing, which amounts to doing something like this:
testAsynchronousFunction: function(){ with(this){
var output = null;
asyncFunc(function(transport){output = transport});
wait(10000, function(){
assertNotNull(output);
});
}}
The problem being that sometimes asyncFunc will take 7 seconds to complete, in which case your test waits 3 seconds too long, and sometimes it takes 12 seconds to complete, in which case your test fails.
There's two ways to get around this if your asynchronous function takes a callback. If the function is for setup, you can run the test creation in the callback, so the test doesn't run until everything's setup:
asyncFunc(function(transport){
new Test.Unit.Runner({
...
}, {testlog: 'testlog'});
});
However, that won't work out of the box because unittest.js uses Event.observe(window, 'load'...) to bring up the test, so if the callback is executed after window load, your SOL. Since I'm using Lowpro, it was easy to circumvent that by using Mr. Webb's slightly different Event.onReady, which is a hook to Prototype's new dom:loaded, with the difference that it will execute immediately if dom:loaded has already been fired.
unittest.js:
Test.Unit.Runner = Class.create({
initialize: function(testcases) {
var options = this.options = Object.extend({
testLog: 'testlog'
}, arguments[1] || {});
options.resultsURL = this.queryParams.resultsURL;
options.testLog = $(options.testLog);
this.tests = this.getTests(testcases);
this.currentTest = 0;
this.logger = new Test.Unit.Logger(options.testLog);
Event.onReady(function() { // Ensures the event fires even if onReady already happened. Requires lowpro.
this.runTests.bind(this).delay(0.1);
}.bind(this));
},
More often than not, that doesn't cut it. So I patched unittest.js to support asynchronous test with wait(null, ...) and continueTest(). Here's how it works, the patch is below. It requires Prototype 1.6.
testAsynchronousFunction: function(){ with(this){
var output = null;
asyncFunc(function(transport){output = transport; continueTest()});
wait(null, function(){
assertNotNull(output);
});
}}
Here's the patch:
Index: unittest.js
===================================================================
--- unittest.js (revision 6610)
+++ unittest.js (working copy)
@@ -205,16 +205,21 @@
}
},
+ waiting: 0,
runTests: function() {
var test = this.tests[this.currentTest], actions;
if (!test) return this.finish();
if (!test.isWaiting) this.logger.start(test.name);
test.run();
- if(test.isWaiting) {
+ if(test.isWaiting && this.timeToWait) {
this.logger.message("Waiting for " + test.timeToWait + "ms");
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
return;
+ } else if (test.isWaiting){
+ this.logger.message("Waiting for continue event.");
+ document.observe('unittest:continue:' + ++this.waiting, this.runTests.bind(this))
+ return;
}
this.logger.finish(test.status(), test.summary());
@@ -466,6 +471,11 @@
this.test = nextPart;
this.timeToWait = time;
},
+
+ waiting: 0,
+ continueTest: function(){
+ document.fire('unittest:continue:' + ++this.waiting);
+ },
run: function(rethrow) {
try {
Leave a Reply