Now that we know unit testing best practices; and we have a o2.Unit test runner to do assertions, run and report our test suites; and we know how to construct test suites to be used in this test suite runner; it’s time to develop a methodology that’ll enable us execute all of our test suites, with a single click.
That’s what we are going to do in this tutorial:
- We will run a list of test suites one by one,
- And we will aggregate the results of each run.
Navigation
This is Part 4 of a 4-part Series on JavaScript Unit Testing. Here are links to all of the parts:
- Unit Testing with JavaScript — Part 1: Best Practices
- Unit Testing with JavaScript — Part 2: Preparation
- Unit Testing with JavaScript — Part 3: Getting Your Feet Wet
- Unit Testing with JavaScript — Part 4: Automation
Modifications in o2.Unit
Let’s start with a minor modification in o2.Unit class.
...
/**
* @function {static} o2.Unit.getGlobalSuccessCount
*
* <p>Gets the total number of successful assertions so far.</p>
*
* @return the total number of successful assertions.
*/
getGlobalSuccessCount : function() {
return state.globalSuccessCount;
},
/**
* @function {static} o2.Unit.getGlobalFailureCount
*
* <p>Gets the total number of failed assertions so far.</p>
*
* @return the total number of failed assertions.
*/
getGlobalFailureCount : function() {
return state.globalFailureCount;
},
...
getGlobalSuccessCount, and getGlobalFailureCount are two public static read-only getters for the module’s state. Having these read-only accessors ensure that we do not (and cannot) change the success and failure count values unintentionally.
Don’t Call Us, We’ll Call You Back
And let us add a globalCompletionCallback to the o2.Unit.run method, which will act as a delegate:
/**
* @function {static} o2.Unit.run
*
* <p>Asynchronously runs all of the registered
* <code>UnitTest</code>s, one after another.</p>
*
* @param {Function} globalCompletionCallback - (Optional) this callback
* will be run with <code>o2.Unit</code> as a parameter passed to it.
*/
run : function(globalCompletionCallback) {
if(o2.Unit.isRunning) {
return;
}
o2.Unit.isRunning = true;
var kCheckInterval = config.TEST_CHECK_INTERVAL;
var oncomplete = globalCompletionCallback ? globalCompletionCallback : o2.nill;
initializeDebugger();
var activeUnitTest = null;
setTimeout(function waitForUnitTest() {
if(isLocked(activeUnitTest)) {
setTimeout(waitForUnitTest, kCheckInterval);
return;
}
// Grab the currently active UnitTest.
activeUnitTest = state.tests.shift();
var isSuiteComplete = !activeUnitTest || !activeUnitTest instanceof UnitTest;
if(isSuiteComplete) {
reportGlobalCompletion();
o2.Unit.isRunning = false;
// We are done with this unit test, so release the lock.
activeUnitTest = null;
oncomplete(o2.Unit);
return;
}
if(hasMoreItems(activeUnitTest)) {
execute(activeUnitTest);
setTimeout(waitForUnitTest, kCheckInterval);
return;
}
// We are done with this unit test, so release the lock.
activeUnitTest = null;
}, kCheckInterval);
}
This globalCompletionCallback will be fired when all the unit tests in the test suite are run, which will enable us to pass tests’ state data to a controller object in another tier. We’ll see that shortly:
the Test Suite Runner
Now let’s create a simple runner that’ll consequentially run a set of test suites.
The HTML is simple:
//tests/runner.html
<!DOCTYPE html>
<html>
<head>
<!--
This program is distributed under
the terms of the MIT license.
Please see the LICENSE file for details.
-->
<title>o2.js Unit Test Runner</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<link rel="stylesheet" type="text/css" href="../o2.js/o2.unit.css" />
<link rel="stylesheet" type="text/css" href="css/demo.css" />
</head>
<body>
<h1>o2.js Unit Test Runner</h1>
<div id="Output"></div>
<iframe id="TestFrame" style="display:none;"></iframe>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.stringhelper.core.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.debugger.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.domhelper.core.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.domhelper.scroll.js"></script>
<script type="text/javascript" encoding="utf-8" src="js/runner.js"></script>
</body>
</html>
Here we have a hidden IFRAME to inject the test suites one by one. The div with id “output”, as its name implies, is the place we’ll display the aggregated results.
the Execution Queue
Let’s define the modules we will test and put them in a collection:
/*global o2*/
( function(window, UNDEFINED) {
...
// @formatter:off
var queue = [
'o2.ajaxcontroller',
'o2.ajax',
'o2.ajaxstate',
'o2.collectionhelper',
'o2.cookie',
'o2.debugger',
'o2.domhelper.class',
'o2.domhelper.core',
'o2.domhelper.dimension',
'o2.domhelper.form',
'o2.domhelper.load',
'o2.domhelper.ready',
'o2.domhelper.scroll',
'o2.domhelper.style',
'o2.domhelper.traverse',
'o2.effect',
'o2.eventhandler.core',
'o2.eventhandler.extend',
'o2.extend',
'o2',
'o2.jsonpcontroller',
'o2.jsonp',
'o2.jsonpstate',
'o2.methodhelper.core',
'o2.methodhelper.extend',
'o2.objecthelper',
'o2.queryparser',
'o2.sortdelegate',
'o2.stringhelper.core',
'o2.stringhelper.encode',
'o2.stringhelper.strip',
'o2.stringhelper.transform',
'o2.supports',
'o2.template',
'o2.try',
'o2.unit',
'o2.validator.core',
'o2.validator.regexp'
];
// @formatter:on
...
We will be sequentially running a test suite for each item in the collection above.
How to Persist the State Data
And let’s hold the total success count, total failure count, and the unit test suite that’s currently being processed in a static context:
...
var state = {
currentQueueItem : null,
totalSuccessCount : 0,
totalFailureCount : 0,
results : {}
};
...
Consuming the Queue
Once the runner starts, it’ll never stop until all of the test suites in the queue are processed:
...
var Runner = window.Runner = {
start : function() {
var kOutputContainerId = 'Output';
init(kOutputContainerId, true);
log('<p>Started <b>"Test Suite Runner"</b>.</p>');
run();
log('<p>Started <b>"Test Suite Runner"</b>.</p>');
run();
}
};
Runner.start();
...
Where init and log are the methods of o2.Debugger.
init, initializes the debugger to print its output to the Output div and to the debug console of the browser, if any.
Then the code flow is delegated to the run method:
...
function run() {
var item = queue.pop();
var kFrameId = 'TestFrame';
var kFileExtension = '.html';
state.currentQueueItem = item;
if(!item) {
// @formatter:off
assert(state.totalFailureCount === 0, [
'<p><b>All done!</b> ',
'Total failure count: <b>', state.totalFailureCount, '</b>, ',
'Total success count: <b>', state.totalSuccessCount, '</b>.</p>'
].join(''));
// @formatter:on
return;
}
var url = [item, kFileExtension].join('');
var frame = document.getElementById(kFrameId);
frame.src = url;
}
...
The method, gets the next item in queue, prepares a URL out of it and assigns it as the source of the TestFrame.
And if there’s no item to process, the method prints the grand total of failures and successes.
So if the item is o2.ajaxcontroller the url will be o2.ajaxcontroller.html.
Let’s see o2.ajaxcontroller.html, to have a deeper look:
<!DOCTYPE html>
<html>
<head>
<!--
This program is distributed under
the terms of the MIT license.
Please see the LICENSE file for details.
-->
<title>o2.AjaxController Unit Test</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<link rel="stylesheet" type="text/css" href="../o2.js/o2.unit.css" />
<link rel="stylesheet" type="text/css" href="css/demo.css" />
</head>
<body>
<h1>o2.AjaxController Unit Test</h1>
<div id="Output"></div>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.debugger.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.stringhelper.core.js"></script>
<script type="text/javascript" encoding="utf-8" src="../o2.js/o2.unit.js"></script>
<script type="text/javascript" encoding="utf-8" src="suite/o2.ajaxcontroller.js"></script>
</body>
</html>
The HTML has a similar structure. There’s an Output layer, where assertion results are displayed. The suite logic resides in suite/o2.ajaxcontroller.js
/*
* <!--
* This program is distributed under
* the terms of the MIT license.
* Please see the LICENSE file for details.
* -->
*/
/*global o2, Demo*/
( function(o2, window, UNDEFINED) {
/*
* Aliases.
*/
var add = o2.Unit.add;
var run = o2.Unit.run;
var assertStrictEqual = o2.Unit.assertStrictEqual;
var assertEqual = o2.Unit.assertEqual;
var assert = o2.Unit.assert;
/**
*
*/
var Suite = {
/**
*
*/
init : function() {
add('dummy test case', {
count : 1,
test : function() {
var me = this;
assert(me, false, 'I pass.');
}
});
run(parent && parent.Runner && parent.Runner.processCompletedSuite);
}
};
Suite.init();
}(o2, this));
It has a dummy test suite that will certainly fail. The passed callback to the run method is Runner’s processCompletedSuite method, which simply displays the currently finished test suite’s results and prepares the next test suite to be tested:
...
processCompletedSuite : function(unit) {
var successCount = unit.getGlobalSuccessCount();
var failureCount = unit.getGlobalFailureCount();
state.totalSuccessCount += successCount;
state.totalFailureCount += failureCount;
// @formatter:off
assert(failureCount===0, [
'<p>Test suite <b>"<a href="',
state.currentQueueItem,
'.html">', state.currentQueueItem, '</a>"</b> has been completed. ',
'Succes count: <b>', successCount, '</b> failure count: <b>', failureCount, '</b>.</p>'
].join(''));
// @formatter:on
scrollToBottom();
next({
successCount : unit.getGlobalSuccessCount(),
failureCount : unit.getGlobalFailureCount()
});
},
...
Where the next method simply shifts the current queue item, and restarts the whole process with the next test suite in queue by calling run:
/**
*
*/
function next(meta) {
var successCount = meta.successCount;
var failureCount = meta.failureCount;
state.results[state.currentQueueItem] = {
successCount : successCount,
failureCount : failureCount
};
run();
}
Since all the test suites have dummy test cases that are intentionally designed to fail, runner.html will print a set of failulres as follows:
And clicking links in that aggregated result set will run the corresponding test suite.
This will give an outcome similar to the following:
Conclusion
In this tutorial, we have created a test suite runner, iterating through a collection of test suites.
These test suites had unit tests with dummy assertions, which will fail by default.
Next Up?
The next step will be to create “real” test suites, with unit tests and assertions for all of the public methods of o2.js objects. You can watch this github branch and observe how o2.js unit tests evolve in time
.
Get the Code
You can get the recent version of this demo at:
https://github.com/v0lkan/o2.js/tree/master/tests.
That’s all for now
Hope you liked it.
Feel free to share your comments and suggestions
.



