Test.More - A framework for writing test scripts
<pre id="test">
<script type="text/javascript">
plan({ tests: numTests });
// or
plan({ noPlan: true });
// or
plan({ skipAll: reason });
// Various ways to say "ok"
ok(got == expected, testDescription);
is(got, expected, testDescription);
isnt(got, expected, testDescription);
// Rather than document.write("# here's what went wrong\n")
diag("here's what went wrong");
like(got, /expected/, testDescription);
unlike(got, 'expected', testDescription);
cmpOK(got, '==', expected, testDescription);
isDeeply(complexStructure1, complexStructure2, testDescription);
if (!haveSomeFeature) skip(why, howMany);
else {
ok( foo(), testDescription );
is( foo(42), 23, testDescription );
}
TODO: {
todo(why, howMany);
ok( foo(), testDescription );
is( foo(42), 23, testDescription );
};
canOK(module, method);
isaOK(object, class);
pass(testDescription);
fail(testDescription);
isSet(gotArray, expectedArray, testDescription);
</script>
</pre>
STOP! If you're just getting started writing tests, have a look at Test.Simple first. This is a drop in replacement for Test.Simple that you can switch to once you get the hang of basic testing.
The purpose of this module is to provide a wide range of testing utilities. Various ways to say "ok" with better diagnostics, facilities to skip tests, test future features and compare complicated data structures. While you can do almost anything with a simple ok()
function, it doesn't provide good diagnostic output.
Before anything else, you need a an HTML element in which to run the tests, and you need a testing plan. The HTML element must have the ID "test" so that, when all test finish running, Test.More can check everything over and possibly output a test status message in the event that something has gone wrong.
A testing plan declares how many tests your script is going to run to protect against premature failure. The preferred way to create a testing plan is to use the plan()
function:
plan({ tests: numTests });
There are rare cases when you will not know beforehand how many tests your script is going to run. In this case, you can declare that you have no plan. (Try to avoid using this as it weakens your test.)
plan({ noPlan: true });
In some cases, you'll want to completely skip an entire testing script.
plan({ skipAll: reason });
Your script will declare a skip with the reason why you skipped and exit immediately with a zero (success).
You can also calculate the number of tests:
plan({ tests: someArray.length });
or to decide between running the tests at all:
if (!window.XMLHttpRequest) {
plan({ skipAall: 'Test irrelevant in IE' });
} else {
plan({ tests: 42 });
}
By convention, each test is assigned a number in order. This is largely done automatically. However, it's often very useful to assign a description to each test. Which would you rather see:
ok 4
not ok 5
ok 6
or
ok 4 - basic multi-variable
not ok 5 - simple exponential
ok 6 - force == mass * acceleration
The latter gives you some idea of what failed. It also makes it easier to find the test in your script: simply search for "simple exponential".
All test functions take a description argument. It's optional, but highly suggested that you use it.
The basic purpose of this library is to print out either "ok #" or "not ok #" depending on whether a given test succeeded or failed. Everything else is just gravy.
All of the following functions print "ok" or "not ok" depending on if the test succeeded or failed. They all also return true or false, respectively.
ok(got == expected, testDescription);
got == expected
is just a simple example) and uses its value to determine whether the test succeeded or failed. A true expression passes, a false one fails. Very simple. ok( exp{9} == 81, 'simple exponential' );
ok( p.tests == 4, 'saw tests' );
ok( items.length, 'items populated');
testDescription
is a very short description of the test to be printed out. It makes it very easy to find a test in your script when it fails and gives others an idea of your intentions. testDescription
is optional, but we very strongly encourage its use. not ok 18 - sufficient mucus
# Failed test 18 (foo.t at line 42)
is ( got, expected, testDescription );
isnt( got, expected, testDescription );
// Is the ultimate answer 42?
is( ultimateAnswer(), 42, "Meaning of Life" );
// foo isn't empty
isnt( foo, '', "Got some foo" );
ok( ultimateAnswer() == 42, "Meaning of Life" );
ok( foo != '', "Got some foo" );
var foo = 'waffle', bar = 'yarblokos';
is( foo, bar, 'Is foo the same as bar?' );
not ok 17 - Is foo the same as bar?
# Failed test (foo.t at line 139)
# got: 'waffle'
# expected: 'yarblokos'
true
or false
! // XXX BAD!
is( isFinite(brooklyn['trees']), true, 'Brooklyn has finite trees');
isFinite(brooklyn['trees'])
is true, it checks if it returns true
. Very different. Similar caveats exist for false
and 0. In these cases, use ok(). is( isFinite(brooklyn['trees']), 'Brooklyn has finite trees');
like( got, /expected/, testDescription );
/expected/
. like(got, /expected/, 'got is expected');
ok( /expected/.test(got), 'got is expected');
//
or new RegExp()
) or as a string that looks like a regex: like(got, 'expected', 'got is expected');
like([], /expected/, 'this will fail');
unlike( this, /that/, testDescription );
cmpOK( got, op, expected, testDescription );
// ok( got.toString() == expected.toString() );
cmpOK( got, 'eq', expected, 'got eq expected' );
// ok( got == expected );
cmpOK( got, '==', expected, 'got == expected' );
// ok( got && expected );
cmpOK( got, '&&', expected, 'got && expected' );
// ...etc...
not ok 1
# Failed test (foo.html at line 12)
# '23'
# &&
# undef
cmpOK( bigHairyNumber, '==', anotherBigHairyNumber );
canOK(class, method, method, method...);
canOK(object, method, method, method...);
canOK('Foo', 'foo', 'bar', 'whatever');
for (var meth in ['foo', 'bar', 'whatever']) {
canOK('Foo', meth);
}
isaOK(object, class, objectName);
object
is a class
. Also checks to make sure the object was defined in the first place. Handy for this sort of thing: var obj = new SomeClass();
isaOK( obj, 'SomeClass' );
var obj = new SomeClass();
ok( defined obj && SomeClass.constructor.prototype.isPrototypeOf(obj));
isaOK( [], 'Array' );
pass(testDescription);
fail(testDescription);
If you pick the right test function, you'll usually get a good idea of what went wrong when it fails. But sometimes it doesn't work out that way. So here we have ways for you to write your own diagnostic messages.
diag(message);
diag(message, message2, message3, ...);
ok( users['foo']), "There's a foo user" )
|| diag("Since there's no foo, check that /etc/bar is set up right");
not ok 42 - There's a foo user
# Failed test (foo.html at line 52)
# Since there's no foo, check that /etc/bar is set up right.
plan({ tests: 1, noDiag: true });
. This is useful if you have diagnostics for personal testing but then wish to make them silent for release without commenting out each individual statement.Sometimes running a test under certain conditions will cause the test script to die. A certain function or method might not be implemented (such as window.XMLHttpRequest()
in Internet Explorer), some resource isn't available (like a net connection) or a JavaScript class isn't available. In these cases it's necessary to skip tests, or declare that they are supposed to fail but will work in the future (a todo test).
For more details on the mechanics of skip and todo tests see Test.Harness.
The way Test.More skips tests is with a named block. Basically, a block of tests that can be skipped over or made todo. It's best if I just show you...
if (condition) skip(why, howMany);
else {
// ...normal testing code goes here...
}
howMany
tests to skip, why
and under what condition
to skip them. An example is the easiest way to illustrate: if (!window.XMLHttpRequest) skip("No XMLHttpRequest in your browser.", 1);
else {
isaOK(new XMLHttpRequest(), 'XMLHttpRequest');
}
window.XMLHttpRequest()
, we skip the test by calling skip()
. Test.More will output special ok
s that Test.Harness interprets as skipped, but passing, tests. If the browser does support window.XMLHttpRequest()
, then obviously the tests will be run as normal.howMany
accurately reflects the number of tests to skip so the number of tests run will match up with your plan. If your plan is "noPlan", howMany
is optional and will default to 1. TODO: {
todo(why, howMany);
// ...normal testing code goes here...
}
TODO: {
todo("URIGeller not finished", 2);
var card = "Eight of clubs";
is( URIGeller.yourCard(), card, 'Is THIS your card?' );
var spoon;
URIGeller.bendSpoon();
is( spoon, 'bent', "Spoon bending, that's original" );
}
howMany
specifies how many tests are expected to fail. Test.More will run the tests normally, but print out special flags indicating they are "todo" tests. Test.Harness will interpret these failures as ok. Should any todo test, it Test.Harness will report it as an unexpected success. You then know the thing you had todo is done and can remove the call to todo().howMany
argument to todo()
. When the block is empty, delete it. if (condition) todoSkip(why, howMany);
else {
// ...normal testing code...
}
skip()
except the tests will be marked as failing but todo. Test.Harness will interpret them as passing. if (condition) skipRest(why);
// ...normal testing code
skip(why, howMany)
, but you don't have to count how many tests to skip to get to the end of the test file.Not everything is a simple equality check or regular expression comparison. There are times you need to see if two arrays are equivalent, for instance. For these instances, Test.More provides a handful of useful functions.
isDeeply( got, expected, testDescription );
got
and expected
are arrays or objects, it does a deep comparison, walking each data structure to see if they are equivalent. If the two structures are different, isDeeply() will output diagnostics identifying where they start differing. isSet(gotArray, expectedArray, testDescription);
Sometimes the Test.More interface isn't quite enough. Fortunately, Test.More is built on top of Test.Builder, which provides a single, unified back end for any test library to use. This means two test libraries that both use Test.Builder can be used together in the same program.
If you simply want to do a little tweaking of how the tests behave, you can access the underlying Test.Builder object like so:
var testBuilder = Test.More.builder();
var timeout = 3000;
var asyncID = beginAsync(timeout);
window.setTimeout(
function () {
Test.ok(true, "Pass after 2 seconds");
endAsync(asyncID);
}, timeout - 1000
);
window.setTimeout()
or window.setInterval()
in a browser, or by making an XMLHttpRequest call. In such cases, the tests might normally run after the test script has completed, and thus the summary message at the end of the test script will be incorrect--and the test results will appear after the summary.Michael G Schwern <schwern@pobox.com> with much inspiration from Joshua Pritikin's Test module and lots of help from Barrie Slaymaker, Tony Bowden, blackstar.co.uk, chromatic, Fergal Daly and the perl-qa gang. JavaScript implementation by David Wheeler <david@kineticode.com>.
Copyright 2001, 2002, 2004 by Michael G Schwern <schwern@pobox.com>, 2005 by David Wheeler.
This program is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License or the GNU GPL.
See http://www.perl.com/perl/misc/Artistic.html and http://www.gnu.org/copyleft/gpl.html.