Skip to content

Instantly share code, notes, and snippets.

@gburgett
Created January 12, 2012 06:51
Show Gist options
  • Save gburgett/1599105 to your computer and use it in GitHub Desktop.
Save gburgett/1599105 to your computer and use it in GitHub Desktop.
QUnit asyncTest timer plugin
<!doctype html>
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<!--[if lt IE 7 ]> <html lang="en" class="ie6"> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="ie7"> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="ie8"> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="ie9"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" ><!--<![endif]-->
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>QUnit test timer addon tests</title>
<link rel="stylesheet" href="qunit-1.10.0.css" type="text/css" media="screen">
<script type="text/javascript" src="qunit-1.10.0.js"></script>
<script type="text/javascript" src="qunit.testtimer.js"></script>
</head>
<body>
<h1 id="qunit-header">asyncTest timeout plugin tests</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup</div>
<div id="cssTests"></div>
</body>
<script type="text/javascript">
TestTimer.defaultTimeout=2000;
asyncTest("simple timed test", function(){
var test = new TestTimer();
setTimeout(function(){
if(test.done) return;
ok(true, "didn't time out before callback");
test.continue();
ok(true, "callback continues to run after continue");
}, 1000);
setTimeout(function(){
if(test.done) return;
ok(false, "test runner should restart before second callback");
start();
}, 1020);
});
asyncTest("test timing out, expect 'test timed out' failure", 2, function(){
var test = new TestTimer();
ok(true, "next assert should be failure, expect to see 'test timed out'");
setTimeout(function(){
if(test.done) return;
ok(false, "FAILURE!! test should have timed out instead of hitting this assert");
test.continue();
}, 3000);
});
asyncTest("test two callbacks", 2, function(){
var test = new TestTimer(2000,2);
setTimeout(function(){
if(test.done) return;
ok(true, "first callback happens before done");
test.continue();
}, 1000);
setTimeout(function(){
if(test.done) return;
ok(true, "second callback happens before done");
test.continue();
}, 1100);
setTimeout(function(){
if(test.done) return;
ok(false, "should not happen before done");
test.continue();
}, 1500);
});
asyncTest("test two callbacks with timeout, expect 'test timed out' failure", 2, function(){
var test = new TestTimer(2000, 2);
setTimeout(function(){
if(test.done) return;
ok(true, "first callback happens, next assert should fail with 'test timed out'");
test.continue();
}, 500);
setTimeout(function(){
if(test.done) return;
ok(false, "FAILURE!!! expect 'test timed out' instead of this assert");
test.continue();
}, 2500);
});
asyncTest("test set timeout value, expect 'test timed out' failure", 2, function(){
var test = new TestTimer(1000);
ok(true, "next assert should be a failure, expect 'test timed out'");
setTimeout(function(){
if(test.done) return;
ok(false, "FAILURE!!!! should have timed out instead of hitting this assert");
}, 1200);
});
asyncTest("test abort, expect 'test aborted' failure", 2, function(){
var test = new TestTimer();
setTimeout(function(){
if(test.done) return;
ok(true, "callback happens before done, expect next assert to fail with 'test aborted'");
test.abort();
}, 1000);
setTimeout(function(){
if(test.done) return;
ok(false, "FAILURE!!!! should have aborted instead of hitting this assert");
}, 1200);
});
asyncTest("test abort, expect 'custom test abort message' failure", 2, function(){
var test = new TestTimer();
setTimeout(function(){
if(test.done) return;
ok(true, "callback happens before done, expect next assert to fail with 'custom test abort message'");
test.abort("custom test abort message");
}, 1000);
setTimeout(function(){
if(test.done) return;
ok(false, "FAILURE!!!! should have aborted instead of hitting this assert");
}, 1200);
});
asyncTest("test abort after timeout, expect 'test timed out' failure and abort does nothing", 2, function(){
var test = new TestTimer();
ok(true, "the next assert should be a 'test timed out' failure");
setTimeout(function(){
test.abort();
}, 2500);
});
asyncTest("test finish, before timeout, immediately finishes", 1, function(){
var test = new TestTimer();
setTimeout(function(){
if(test.done) return;
ok(true, "callback was hit");
test.finish();
}, 1000);
setTimeout(function(){
if(test.done) return;
ok(false, "should have finished before hitting this callback");
}, 1100);
});
asyncTest("new test started before old test finished", 2, function(){
var test = new TestTimer();
setTimeout(function(){
if(test.done) return;
ok(false, "first test should have been killed by new test");
test.finish();
}, 1200);
setTimeout(function(){
ok(true, "Expect to kill old test quietly");
var test2 = new TestTimer();
setTimeout(function(){
if(test2.done) return;
ok(true, "New test should run just fine");
test2.finish();
});
}, 1000);
});
asyncTest("test catch all", 1, function(){
setTimeout(function(){
ok(true, "catch all finished");
start();
}, 10000);
});
</script>
</html>
/*
A simple async test timer for QUnit.
Calling 'asyncTest.start()' starts a timeout and returns a test object. This test object has a couple important methods, the first of which is 'continue'. 'continue()' is used anytime you are doing asserts in a callback. Once you've called 'continue' the number of times specified as a start parameter (default 1), Testtimer will automatically cancel the timeout and restart the QUnit test runner.
The test object also has a 'done' boolean property. This property should be checked before performing asserts in callbacks to make sure your asserts dont end up in a separate test. See examples in test file.
The test object has one other method, 'abort()'. This method immediately aborts the currently running asyncTest and begins the next one. The timeout will be cleared and 'ok(false, "async test aborted")' will be called, and the 'done' property would be set. Any async callbacks in the test should make sure to check the 'done' object as I have already described to make sure they arent processing after an abort.
*/
(function(w){
var topId = 1;
var current;
TestTimer = function(timeout, expect){
if(current != null)
current.kill();
var self = this;
var timedOut = function(){
if(self.done)
return;
console.log(self.id + " timed out");
if(self.remaining >= 0){
ok(false, "test timed out");
self.finish();
}
}
//create the response object
this.wait = timeout ? timeout : TestTimer.defaultTimeout;
this.remaining = expect ? expect : 1;
this.done = false;
this.id = topId;
topId++;
current = this;
console.log(this.id + " starting");
this.timer = setTimeout(function() {timedOut()}, this.wait);
return this;
}
TestTimer.defaultTimeout = 2000;
TestTimer.prototype = {
continue: function(){
//ignore continues in the wrong context
if(this.done)
return;
this.remaining--;
if (this.remaining <= 0 && this.timer != null){
console.log(this.id + " continues complete");
this.finish();
}
},
abort: function(message){
//ignore aborts in the wrong context
if(this.done)
return;
if(!message) message = "async test aborted";
console.log(this.id + " aborting, msg: " + message);
if(this.remaining >= 0){
ok(false, message);
this.finish();
}
},
finish: function(){
if(this.done)
return;
console.log(this.id + " finished, killing");
this.kill();
current = null;
//restart test runner since we're done
console.log(this.id + " restarting test runner");
start();
},
kill: function(){
if(this.done)
return;
if(this.timer != null)
clearTimeout(this.timer);
this.timer = null;
this.done = true;
console.log(this.id + " killed");
//do not restart test runner
},
}
})(window);
@gburgett
Copy link
Author

Testtimer is a qunit plugin that allows easy timing and restarting of qunit async tests. The qunit test .html file shows usage patterns.

The general idea is calling 'asyncTest.start()' starts a timeout and returns a test object. This test object has a couple important methods, the first of which is 'continue'. 'continue()' is used anytime you are doing asserts in a callback. Once you've called 'continue' the number of times specified as a start parameter (default 1), Testtimer will automatically cancel the timeout and restart the QUnit test runner.

The test object also has a 'done' boolean property. This property should be checked before performing asserts in callbacks to make sure your asserts dont end up in a separate test. See examples in test file.

The test object has one other method, 'abort()'. This method immediately aborts the currently running asyncTest and begins the next one. The timeout will be cleared and 'ok(false, "async test aborted")' will be called, and the 'done' property would be set. Any async callbacks in the test should make sure to check the 'done' object as I have already described to make sure they arent processing after an abort.

@gburgett
Copy link
Author

The test object now has a '.finish()' property which immediately stops the timeout and re-starts the test runner. Also fixed an issue with '.abort()' leaking into the next test's test object.

@gburgett
Copy link
Author

The test object now has a 'kill()' property which immediately kills the async test quietly, without an assertion. Starting a new test now kills the previous test instead of aborting it.

@gburgett
Copy link
Author

gburgett commented Dec 1, 2012

Refactored to use its own object with a constructor rather than attaching to the asyncTest function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment