Hiro is a small yet powerful unit testing framework for JavaScript. It runs each test suite in a separate iframe sandbox, preventing global state leaks and conflicts.
Example: Hiro testing itself (run it):

Hiro is a small yet powerful unit testing framework for JavaScript. It runs each test suite in a separate iframe sandbox, preventing global state leaks and conflicts.
Example: Hiro testing itself (run it):

Hiro's usage pattern resembles patterns used by similar frameworks in other languages such as Python (PyUnit) and Java (JUnit). You create test suites, load fixtures, write test cases, etc. Let's start with the test suites.
Test suites, in Hiro, are created using the hiro.module
method. The method accepts two parameters: name of the suite and its
implementation in form of a JavaScript object.
hiro.module('MySuite', {
setUp: function () { /* ... */ },
testFeatureA: function () { /* ... */ },
testFeatureB: function () { /* ... */ }
});
The example above should be pretty self-explanatory. We created a simple
test suite that defines one special method, setUp, and two
test cases: testFeatureA and testFeatureB. Hiro
supports three types of properties for its test suites: control properties,
test cases and everything else. Control properties are hooks and helpers that
you can use to configure the suite's behavior. Test cases are, well, test
cases—the basic building blocks of unit testing. All other properties
and methods are ignored by Hiro and can be used as user-defined helper
functions.
Let's go over all control properties that are available today:
| setUp | This is a hook method which Hiro will automatically call when we run the suite. |
| waitFor | This is a hook method which Hiro will use to check if the suite is
ready to be executed. If this method is defined, the suite will be paused
and Hiro will start calling waitFor continuously until it
returns a truthy value (or until the timeout treshold is met). As soon as
this happens, Hiro will start executing test cases. |
| onTest | This is a hook method which Hiro will automatically call before each test
in a suite. The this object inside of the method exposes
one special property called args. You can assign an array to
this property and this array will later be passed into each test as a list of
formal parameters.
|
| mixin | This is a special property that can be used to tell Hiro to mixin
other suites into the current one. It accepts an array of strings, each
string representing a name of a suite to mixin from.
When creating a suite instance, Hiro will copy all properties
and methods from MyParentSuite into the current one, then it will copy
everything from AnotherParentSuite, and then it will overwrite
them with those defined in the current suite (if any).
|
| test* | All methods with names starting with test will be recognized as
test cases. Test cases are single scenarios that must be set up and checked
for correctness.
|
After we created our suite, we need to load a fixture and make sure that
our environment is initialized. For the former task we will use an instance
method called loadFixture that is accessible from inside of our
setUp implementation.
hiro.module('MySuite', function () {
setUp: function () {
this.loadFixture('mylib');
},
// ...
});
That's it! To hold on our suite execution until our environment is
initialized we will use the method waitFor.
hiro.module('MySuite', {
waitFor: function () {
// this.window here refers to the sandboxed environment
return this.window.MyLib && this.window.myLib.isReady;
},
// ...
});
Now let's walk through the process of creating a fixture. A fixture in Hiro
is simply an HTML document that is injected into a newly created iframe. In order
to define a fixture, we will need to write the document's code into a textarea
HTML element with class attribute set to “fixture” and
data-name attribute set to whatever name we picked for that fixture
(for our examples above, the name will be “mylib”).
All fixtures must be defined in the same file as Hiro itself.
<textarea class="fixture" data-name="mylib">
<html>
<head>
<script src="mylib.js">
</head>
<body>
<!-- ... -->
</body>
</html>
</textarea>
All the things we described so far are used to prepare the environment. As we
mentioned above, the basic building blocks of unit testings are test cases. Each
test case in Hiro, as well as the special hook method onTest, has
access to the sandboxed environment created for its suite. References to that
environment—in form of window and document
objects—are available from within test cases as instance properties (i.e.
properties of this).
hiro.module('MySuite', {
// ...
testSomeMethod: function () {
// Here, `window` refers to the global environment
// (where Hiro is loaded) and `this.window` refers
// to the sandboxed environment with your fixture
// code in it. Same with `this.document`.
window.hiro == null; // false
window.MyLib == null; // true
this.window.hiro == null; // true
this.window.MyLib == null; // false
},
// ...
});
In addittion to aforementioned window and document
properties each instance of a test case contains some other useful methods.
| expect |
|
| pause |
resume() method later in the code or the test
will fail with a timeout error. |
| resume |
|
| assertTrue |
|
| assertFalse |
|
| assertEqual |
|
| assertException |
fn throws an exception. Accepts
an optional second parameter, an exception object, to test for a
specific exception. |
| assertNoException |
fn executes without
raising any exceptions. |
After we created a test suite, loaded our fixture and wrote a couple of tests,
we might want to actually run them. For that, we can use the method
hiro.run. It accepts two optional arguments: a suite name and a test
name. Without any arguments, Hiro will run all suites and tests. With one
argument (a suite name), it will run all tests within that suite. And with
both arguments provided, it will run just a specific test.
There is also another method, hiro.autorun, which reads the
query string and decides what to run based on its value.
hiro.html -> run all suites and tests
hiro.html?MySuite -> run all tests from MySuite
hiro.html?MySuite.testA -> run only testA from MySuite
And that's how you use Hiro. If you are still confused, read through the next section where we will go through a real world example of using Hiro. In fact, that use case was the reason why I wrote Hiro in the first place.
Another great thing about Hiro is that its presentation code is completely separated from the main library. When running tests Hiro fires events and any code can listen to them and report the results in any way they want. We will have documentation for the events soon but, for now, please refer to the web.js file in our repository. This file and test.html define the out-of-the-box presentation of Hiro (the one you see in the screenshot above).
In this section, we will walk through the process of testing a JSON library. The library in question is the one we use at Disqus. All code samples were taken from the actual Disqus repository and adapted for the purpose of this text. Let's start by creating our tests endpoint.
Tests endpoint is an HTML file that loads Hiro and unit tests, and contains all the fixtures our tests need. Its layout is rather simple.
<!DOCTYPE html>
<html lang="en">
<head>
<title>DISQUS JavaScript Tests</title>
<!-- Hiro files -->
<link rel="stylesheet" href="css/tests/hiro.css">
<script src="js/tests/ender.js"></script>
<script src="js/tests/hiro.js"></script>
<script src="js/tests/web.js"></script>
</head>
<body onload="hiro.autorun();">
<h1>DISQUS JavaScript Tests</h1>
<!-- Hiro container (web.js needs this) -->
<div id="web">
</div>
<div id="footer">
(music, movies, microcode)
</div>
<!-- Our fixture -->
<textarea class="fixture" data-name="json">
<html>
<head>
<script>
var isReady = false;
// A bunch of disqus configuration variables that were
// removed for the sake of simplicity.
function disqus_config() {
DISQUS.once('thread.onReady', function () {
// This function will be called when Disqus is fully loaded
isReady = true;
});
}
</script>
</head>
<body>
<div id="disqus_thread"></div>
<script>
// Normal Disqus snippet here
(function (doc) {
var dsq = doc.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = 'http://test.disqus/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
}(document));
</script>
</body>
</html>
</textarea>
<!-- Our unit tests -->
<script src="js/tests/unit/json.js"></script>
</body>
</html>
After we finished our endpoint, we can create a test suite and start
writing test cases. Each test case will get a reference to the sandboxed
DISQUS object as its function argument. We could reference that object
with this.window.DISQUS in each test case but the shorter version
is better.
/*jshint undef: true */
/*global hiro: false */
hiro.module('GenericJSONTests', {
setUp: function () {
// Loading the fixture we defined in the previous snippet
this.loadFixture('json');
},
waitFor: function () {
// Wait until Disqus is ready before running this suite
return this.window.isReady;
},
onTest: function () {
// Each test should get a reference to the DISQUS object as an argument
this.args = [ this.window.DISQUS ];
},
testParse: function (DISQUS) {
var valid = '{ "name": "Anton", "company": "Disqus" }';
var invalid = '{ "name": , "company" "Disqus" }';
var obj = DISQUS.json.parse(valid);
this.expect(3);
this.assertEqual(obj.name, "Anton");
this.assertEqual(obj.company, "Disqus");
this.assertException(function () {
DISQUS.json.parse(invalid);
});
},
testStringify: function (DISQUS) {
var obj = { name: "Anton", company: "Disqus" };
var arr = [ "Hello", "World", true, 2 ];
var json = DISQUS.json.stringify(obj);
this.expect(2);
this.assertEqual(json, '{"name":"Anton","company":"Disqus"}');
json = DISQUS.json.stringify(arr);
this.assertEqual(json, '["Hello","World",true,2]');
}
});
Our test cases check that DISQUS.json correctly parses JSON and stringifies JavaScript objects. They also check that, in case of invalid JSON, our library throws an exception.
Now, a few months ago we hit a bug where our library was failing on sites with
Prototype.js v1.5. After some debugging we realized that older versions of that
library implement the Object.toJSON method in a way that breaks
our JSON library. We fixed that bug but in order to make sure that it won't
be re-introduced in future, we need to write a regression test.
We start by creating a new fixture that loads Disqus alongside with Prototype.js v1.5.
<!-- Another fixture, with Prototype -->
<textarea class="fixture" data-name="json-prototype-15">
<html>
<head>
<script src='js/tests/externals/prototype15.js'></script>
<script>
var isReady = false;
// A bunch of disqus configuration variables here,
// They are irrelevant for our example.
function disqus_config() {
DISQUS.once('thread.onReady', function () {
// This function will be called when Disqus is fully loaded
isReady = true;
});
}
</script>
</head>
<body>
<div id="disqus_thread"></div>
<script>
// Normal Disqus snippet here
(function (doc) {
var dsq = doc.createElement('script');
dsq.type = 'text/javascript';
dsq.async = true;
dsq.src = 'http://test.disqus/embed.js';
(document.getElementsByTagName('head')[0] ||
document.getElementsByTagName('body')[0]).appendChild(dsq);
}(document));
</script>
</body>
</html>
</textarea>
After that, we want to re-run our GenericJSONTests suite but with this
new fixture. We do that by creating a new test suite and “mixing”
the original suite into it. The only method we will overwrite is setUp
since we want to load our new fixture with Prototype.js in it.
hiro.module('Prototype15JSONTests', {
mixin: ['GenericJSONTests'],
setUp: function () {
this.loadFixture('json-prototype-15');
}
});
And that's it! Hiro will now load the new fixture and run all tests from
GenericJSONTests again but in the new environment.
I hope you now understand the idea behind Hiro and how it can be used in your own projects. We use it for our JavaScript tests at Disqus and I would love to hear from you if you decided to use it for some of your projects. Feel free to ping me on Twitter and tell what you like or hate about Hiro.
Source code is available on GitHub: antonkovalyov/hiro. Feel free to fork it, submit pull requests, report bugs, etc.
The library itself doesn't have any dependencies. It was designed to work (without modifications) only in the browser environment. The presentation part uses a custom DOM library built with Ender.
Hiro Protagonist is the main character from Neal Stephenson's cult cyberpunk novel “Snow Crash”.