using-testharness.js | |
---|---|
This document provides a tutorial for W3C's test framework, known as This tutorial does not assume that you are necessarily familiar with other test frameworks, but it does expect you to be reasonably proficient with JavaScript (since JavaScript APIs is what one tests using this). If you are familiar with other test frameworks such as QUnit, Mocha, or Jasmine then you should find your way around here relatively easily. Indeed, This tutorial is actually designed to be runnable. The code you see in the right column is described on the left, but it is also ran as soon as you load this page. If you scroll to the bottom, you will see the results of this run. This documentation was generated using Docco, and the idea behind the way in which it is done is shamelessly stolen from the Jasmine Documentation. | |
Getting StartedLet's get started with this code! The first thing you need to do to load
At which point you might rightfully ask why there are two files. The reason for this is
simple: the first one is the actual implementations, and the second one is empty. Why
include an empty file? The idea is that when a specific vendor checks your test suite
out, they can override the content of the If you wish the HTML page into which your tests are being run to render results in a nice
and convenient table, you should include an HTML element with ID | |
Basic Testing | |
The most basic usage relies on the | test(function () {
assert_true(true);
}, "True really is true"); |
The given function must include at least one assertion; conversely, assertions can only appear in the context of a test function. A single test may contain multiple assertions, in which case it is considered to be atomic. That is to say that a single failed assertion will fail the test, whereas all are required to pass for the whole test to pass. A document can contain as many tests as you wish it to. | |
The example here contains two assertions, both of which pass. | test(function () {
assert_true(true);
assert_false(false);
}, "Truth is what you believe it to be"); |
But in this example one passes, while the other fails. This causes the entire test to be reported as a failure. | test(function () {
assert_true(true);
assert_false(true);
}, "All opinions are equally valid."); |
In addition to a function and a name, | |
The only general-purpose option that you can use is | test(function () {
/* do something long and slow here */
assert_true(true);
}, "Long operation is successful", { timeout: 5000 }); |
Note that you do not want to use this for asynchronous operations (if only because it won't work). For those, see the section dedicated to that topic below. | |
Included Assertions | |
There is a good choice of assertions available by default. Most of those take the form
| |
| test(function () {
assert_true(true, "Truth is true");
assert_true(1 === 1, "One is really one");
}, "Simple checks on truth"); |
| test(function () {
assert_false(false, "Falsity is false");
assert_false(1 === 0, "One is not zero");
}, "Simple checks on falsity"); |
| test(function () {
assert_equals("dahut", "da" + "hut", "String concatenation");
assert_equals(42, 6 * 7, "The ultimate answer");
}, "Simple checks on equality"); |
| test(function () {
assert_not_equals("dahut", "myth", "String comparison");
assert_not_equals(42, "42", "The ultimate answer");
}, "Simple checks on unequality"); |
| test(function () {
assert_in_array("dahut",
"chupacabra dahut unicorn".split(" "),
"Dahut hunting");
assert_in_array(2017, [42, 47, 62, 2017] , "Lottery");
}, "Simple checks on membership"); |
| test(function () {
assert_array_equals(["chupacabra", "dahut", "unicorn"],
"chupacabra dahut unicorn".split(" "),
"Dahut hunting");
assert_array_equals([4, 9, 16],
[2, 3, 4].map(function (x) { return x * x; }),
"Square");
}, "Checks on identical membership"); |
| test(function () {
assert_approx_equals(Math.PI, 3.14, 0.01, "Roughly circular");
assert_approx_equals(42, 47, 5, "47 is almost 42");
}, "Checks on epsilon equality"); |
| test(function () {
assert_regexp_match(document.title,
/^\w{5}-\w{10,12}\.js$/,
"That's my title");
assert_regexp_match("A", /a/i, "Matching lowercase");
}, "Checks using regular expressions"); |
| test(function () {
var gollum = { ring: "MIIIIINE!!!!" };
assert_own_property(gollum, "ring", "Tricksy hobbitses!");
/* this will fail even though `gollum` has `toString`. */
assert_own_property(gollum,
"toString",
"I have that property, but it'ssss not mine.");
}, "Checks for property ownership"); |
| test(function () {
var gollum = { ring: "MIIIIINE!!!!" };
/* this will succeed here */
assert_inherits(gollum,
"toString",
"I have that property, but it'ssss not mine.");
assert_inherits(gollum,
"hasOwnProperty",
"This one works too.");
}, "Checks for property inheritance"); |
| |
| test(function () {
assert_readonly(document, "nodeType", "You cannot change nodeType.");
}, "Checks for attribute readonlyness"); |
| |
If | test(function () {
assert_throws(null,
function () { document.appendChild(document); },
"Specific DOM exception.");
}, "Checks for exceptions (null)"); |
If | test(function () {
assert_throws({ name: "Bad Kitten!" },
function () { throw { name: "Bad Kitten!"}; },
"Any exception with the right name.");
}, "Checks for exceptions (object)"); |
If | test(function () {
assert_throws("HierarchyRequestError",
function () { document.appendChild(document); },
"Specific DOM exception.");
}, "Checks for exceptions (string)"); |
| test(function () {
if (true) return "where you came from";
assert_unreached("Can't Touch This");
}, "Simple check on unreachability"); |
Whereas this one fails because the code reaches it. | test(function () {
assert_unreached("Reaching where no coder has reached before");
}, "Failed check on unreachability"); |
Asynchronous TestingIt is increasingly rare for Web APIs to be entirely synchronous. Many of the modern ones are very careful to be asynchronous whenever an operation may take a little time and therefore freeze the main thread. Testing asynchronous APIs is naturally different from testing synchronous code since results from asynchronous calls by their very nature do not follow the nicely linear flow of synchronous calls. Thankfully, | |
First, instead of calling | var stTest = async_test("Testing setTimeout()"); |
We will use our setTimeout call to perform an assertion, and flag that the test is over. This
will cancel the timeout and if the assertion is successful (in our case it trivially is) then
the test passes. This is performed with two operations: first, the | setTimeout(function () {
stTest.step(function () {
assert_true(true, "Truth is asynchronously true.");
});
stTest.done();
}, 10); |
It is often the case that in testing asynchronous code one needs to assign event handlers to
specific | var xhrTest = async_test("Testing XHR access")
, xhr
;
/* this in a step because it could throw */
xhrTest.step(function () {
xhr = new XMLHttpRequest();
xhr.open("GET", "using-testharness.html");
xhr.onreadystatechange = xhrTest.step_func(function (ev) {
assert_true(ev.isTrusted, "readystatechange is a trusted event");
assert_false(ev.bubbles, "readystatechange is does not bubble");
xhrTest.done();
});
xhr.send();
}); |
Including Metadata | |
If you are writing tests for inclusion in the W3C Testing Framework (and if you're writing those tests for a W3C group, then you really should), then this section can be of interest to you. If not, you can safely skip it. | |
In order to best integrate into the Testing Framework, your tests ought to have metadata. If you have only
one test per HTML file, then your test metadata should be contained in the <head> section of your
document and, again, you can safely skip over this section. If, however, you wish to include an entire
test suite in a single document (which you certainly can do) then it is useful to specify test metadata
for every call to | |
This can be achieved very simply by providing the test metadata as part of the third parameter to | test(function () {
assert_true(true, "The spec says it's true.");
}
, "True is true as per spec"
, {
help: ["http://www.w3.org/TR/some-specification/#truth-and-beauty",
"http://www.w3.org/TR/other-specification/#truthiness"]
, assert: "Truth is true, you know."
, author: "Robin Berjon <robin@berjon.com>"
, flags: "svg animated"
}
); |
Advanced UsageMost users should not need this section. Read it if you are trying to achieve something complex that is not working, or if you are curious — but don't worry if it does not seem to make much sense as oftentimes you will not need it. Complex SetupSometimes it is important either to perform complex setup operations for a test run, or
to modify the overall behaviour of the test run, or both. These can be performed by using
the Once the first test has run, any call to | |
Essentially, anything that happens in | setup(function () {
throw new Error("BOOM!");
}); |
The | |
| /* time out after 20 seconds */
setup({ timeout: 20000 }); |
It is important to note that if you wish to use | setup({ explicit_done: true });
/* ... at some point later... */
done(); |
| setup({ output_document: window.parent.contentDocument });
setup({ output_document: document.getElementById("someIFrame").contentDocument }); |
| setup({ explicit_timeout: true });
/* ... at some point later if there really is a time out... */
timeout(); |
Formatting | |
At times when you wish to report issues in a test suite, or simply log something during development,
it can be very valueable to produce output that is more human-oriented than what | format_value(document);
format_value("foo\nbar");
format_value([-0, Infinity]); |
Generating TestsWriting tests can be a very repetitive endeavour. At times, you simply need to call the same assertion on a long list of actual and expected values, and when that happens the overhead of the testing boilerplate, no matter how lightweight it has been made to be, can seem overwhelming. | |
In order to make your life ever so simpler, | generate_tests(assert_equals, [
[ "Square of 2", 2 * 2, 4 ]
, [ "Square of 3", 3 * 3, 9 ]
, [ "Square of 4", 4 * 4, 16 ]
, [ "Square of 5", 5 * 5, 25 ]
, [ "Square of 6", 6 * 6, 36 ]
]
); |
CallbacksAt times it can be useful to know what is going on inside the test harness so that you can react to it and build your own behaviour (for instance, producing your own reports or integrating with a larger testing system that you may be using). For that purpose, | |
| /* in same context */
add_start_callback(function () {
console.log("The tests have started running.");
});
/* in enclosing context */
function start_callback () {
console.log("The tests have started running.");
} |
| /* in same context */
add_result_callback(function (res) {
console.log("Result received", res);
});
/* in enclosing context */
function result_callback (res) {
console.log("Result received", res);
} |
| /* in same context */
add_completion_callback(function (allRes, status) {
console.log("Test run completed", allRes, status);
});
/* in enclosing context */
function completion_callback (allRes, status) {
console.log("Test run completed", allRes, status);
} |
ResultsAs promised this is a self-runnable document that includes the results for the test suite specified by the code in the right column above. You can see the results below:
| |