Unit Tests
API Quick Reference
JavaScript is required to use the quick reference
What are unit tests?
Glow's unit tests are designed to check that individual functions within Glow execute correctly when given a variety of parameters and conditions.
Once a change is made to the code, running unit tests will help catch any negative results of that change.
For example, if a code change caused glow.dom.NodeList to contain duplicate copies of the same Element, this would result in numerous failing unit tests, as NodeLists would be of an unexpected length. There are specific unit tests for NodeList's removal of duplicate items.
Glow's unit test system (originally based on jQuery's) is split into "modules", which contain "tests", which contain "asserts".
A test fails if any of its asserts return an unexpected value, an uncaught error is thrown, or an unexpected number of asserts are received.
Tests may be skipped if required conditions have not been met. For instance, a test which needs to be run before the DOM is ready may skip if the DOM is already ready.
Tests are marked "Todo" if they are expected to fail. Tests are usually written before the code required to pass them, this is known as Test Driven Development.
Running unit tests
Glow's unit tests currently require a web server to run all tests.
Simply upload Glow's trunk (or a branch or tag) to your development
server. Assuming you've uploaded a Glow branch to
http://localhost/glow1/
,
visit the following in a browser:
http://localhost/glow1/test
All tests will begin running by default, or you can select a particular set of tests to run in isolation.
Clicking on a particular test will show / hide its asserts. A failing test will have its asserts shown by default.
Writing Unit Tests
Unit tests are contained within trunk/test/glow/moduleName/moduleName.js
.
The unit tester is aware of these files via the 'testFiles' array in
trunk/test/loadandrun.js.
var testFiles = [
'glow/glow.js',
'glow/dom/dom.js',
'glow/events/events.js',
// etc etc, and our new module...
'glow/moduleName/moduleName.js'
];
Files will be included in the above order, although one unit test must not depend on another.
Unit test format
t.module("glow.moduleName");
t.test("Basic use of moduleName", function() {
// asserts go here
});
t.todo("Advanced use of moduleName", function() {
// asserts go here
});
t.module
This creates a grouping of tests / todos. Anything under a particular t.module call can be run independently of other modules by selecting it from the drop down menu on the unit test page. The parameter is the title of the module of tests.
t.test / t.todo
Tests / todos are groups of asserts. If a single assert within a test fails, the test fails. Todos are simply tests which are expected to fail, usually because the tested features have not yet been implemented. Tests should test an individual method, or a certain use of a particular method. For instance, "NodeList.css setting" and "NodeList.css getting" are separate tests.
Test structure
t.test("Basic use of moduleName", function() {
t.expect(2);
t.ok(glow.moduleName, "glow.moduleName present");
t.equals(glow.moduleName.someValue, 0, "Initial someValue");
});
The following functions are control how a test runs:
t.expect( num )
How many asserts are expected within this test? A test may error before all asserts are called, or a faulty loop may result in too many asserts. By defining how many asserts you're expecting, these problems will be caught.
// expect 4 asserts
t.expect(4);
t.skip( reason )
A test may need to be run under certain conditions. Eg, on a web server or before DOM ready. If these conditions aren't met, the test hasn't failed but it cannot run. In these situations you may skip the test, giving a reason.
if (glow.isReady) {
t.skip("Test must be run before DOM ready");
}
t.stop() & t.start()
Not all tests can run synchronously. If you need a test to run asynchronously you can prevent the next test running by calling t.stop(), once the async actions are complete and asserts have run, call t.start() to resume the test queue.
t.stop();
glow.net.get("testdata/somefile.txt", {
onLoad: function(response) {
t.ok(true, "file downloaded");
t.start();
},
onError: function() {
t.ok(false, "file downloaded");
t.start();
}
});
Asserts
t.ok( bool, name )
The simplest kind of assert. Passes if 'bool' is true.
t.ok( glow.env.version != "", "glow.version populated" );
t.equals( property, expectedValue, name )
Asserts that propery equals expectedValue.
t.equals( glow.lang.trim(" Foo Bar"), "Foo Bar", "Trim leading" );
t.contains( value, substring, name )
Asserts that value contains substring.
t.contains( " " + node.className + " ", " testClass ", "Node has class testClass" );
t.doesNotContain( value, substring, name )
Asserts that value does not contain substring.
t.doesNotContain(
" " + node.className + " ",
" testClass ",
"Node does not have class testClass"
);
t.isSet( array, otherArray, name )
Asserts that array is the same as otherArray. Values are compared using == and only one level deep.
Also works on array-like objects.
t.isSet( nodeList, differentNodeList, "NodeLists match" );
t.isObj( object, otherObject, name )
Asserts that object contains the same properties as otherObject (and vice versa). Values are compared using == and only one level deep.
t.isObj( glow.data.decodeJson(jsonStr), {foo:"bar"}, "Basic decode" );
t.selects( cssSelector, ids, name )
Asserts that cssSelector selects & only selects elements with IDs in array ids.
t.selects( "#nav > li > a", ["linkOne", "linkTwo"], "Selects child links only" );
Test-driven development
Tests are usually written before the code required to pass them, this gives the developer the chance to 'try' the API before actually coding it. Sometimes, major usage problems can be highlighted at this stage.
Once a comprehensive set of tests have been created, the developer has an easy way to check the progress of their code, and can easily see if a new piece of code breaks existing code.