Javascript unit testing with Qunit

Qunit is a javascript testing framework created by the jquery foundation. You can download Qunit and find some more documentation at http://qunitjs.com/ . The site has tutorials and general unit testing info, so I won’t go into details but here is a quick recap to get started. Include qunit and your test.js in your header. Throw in the qunit CSS if you like the fancy red/green colors for your test results.

1
2
3
<script src="resources/qunit-1.13.0.js"></script>
<script src="resources/tests.js"></script>
<link rel="stylesheet" href="resources/qunit-1.13.0.css">

In your tests.js add the following code and presto! Your have some working tests.

1
2
3
4
5
6
7
test( "hello string test", function() {
ok( 1 === "1", "Failed!" );
});

test( "hello integer test", function() {
ok( 1 === 1, "Passed!" );
});

Here’s what that looks like in your browser:

Qunit basic setup

Qunit basic setup

Now let’s add some extensions to Qunit. Link jQuery and the QunitExtensions.js in your header and you’ll get the following extra’s. You could get rid of the jquery, but you’ll have to rewrite some of the extension code. * Next to existing Test and AsyncTest, the extensions adds a SkippedTest. * A handy checkbox that will sort the test results by result type * A checkbox similar to the existing ones that lets you hide/show the SkippedTests * Setup module function which enables you to define setup and teardown functions for your tests.

1
function setupModule(name, setupFunction, tearDownFunction){…}

For the statistics guys/gals I threw in the following code. Which outputs a Junit XML report to the console. The thought behind that is to prepare to dump this report in a CI environment. You’ll have to add the qunit-reporter-junit.js in your header as well.

1
2
3
QUnit.jUnitReport = function(report) {
console.log(report.xml);
};

Here’s an example export:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="file:///D:/cloudstation/JS%20testing/testsuite.html"
hostname="localhost" tests="2" failures="0" errors="0" time="0.053" timestamp="2014-11-06T10:40:37Z">
<testsuite id="0" name="Hello Qunit module" hostname="localhost" tests="2" failures="0"
errors="0" time="0.001" timestamp="2014-11-06T10:40:37Z">
<testcase name="hello string test" tests="1" failures="0" errors="0" time="0" timestamp="2014-11-06T10:40:37Z">
</testcase>
<testcase name="hello integer test" tests="1" failures="0" errors="0" time="0" timestamp="2014-11-06T10:40:37Z">
</testcase>
</testsuite>
<testsuite id="1" name="skipped test module" hostname="localhost" tests="0" failures="0"
errors="0" time="0.001" timestamp="2014-11-06T10:40:37Z">
<testcase name="Igore me (SKIPPED)" tests="0" failures="0" errors="0" time="0.001" timestamp="2014-11-06T10:40:37Z">
</testcase>
</testsuite>
</testsuites>

Here’s what the skipped test looks like in code and browser:

1
2
3
SkippedTest('Igore me',function(){
equal('foo','bar', 'this never happened');
});
Qunit skipped test

Qunit skipped test

Now let’s divide these tests into modules without any setup and teardown functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
setupModule("Hello Qunit module", null, null);

test( "hello string test", function() {
ok( "1" === "1", "Failed!" );
});

test( "hello integer test", function() {
ok( 1 === 1, "Passed!" );
});

setupModule("skipped test module", null, null);
SkippedTest('Igore me',function(){
equal('foo','bar', 'this never happened');
});

Notice that the results display the name of the module they belong to, but more practical is the dropdown at the top allowing you to select a module. This will execute only the tests in that module. At some point you may want to have some benchmarks on your script (functional script, not the tests themselves). So lets do that now. Don’t forget to add references to lodash-compat.js and benchmark.js . Just so we have something to test, I added the RandomIntFromInterval and GenerateCode functions (guess what they do..). In the tests. js file I added a formatOutput function to display the results of the benchmark.

And this code snippet which creates the benchmark suite and adds the two functions to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var suite = new Benchmark.Suite;

// add tests
suite.add('RandomIntFromInterval_benchmark#test', function() {
var Number = RandomIntFromInterval(1, 10);
})

.add('GenerateCode_benchmark#test', function() {
var ColorsEnum = ["yellow", "green", "orange","purple","gray","magenta","red","white","black"];
var mastercode = GenerateCode(ColorsEnum);
})
// add listeners
.on('cycle', function(event) {
//console.log(String(event.target));
$("<div>"+formatOutput(String(event.target))+"</div>").appendTo("#benchmarks");
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
})
.on('start',function(){
// do something when the suite starts
})
// run async
.run({ 'async': true });

This will result in the super helpful results below (notice the pretty layout I added to make it match the Qunit results). I’d advise to wrap the benchmark code in a conditional statement so you can toggle them on or off. You can even go so far as to add another checkbox to control that.

Qunit benchmark

Qunit benchmark

But what about quality you ask. Well let’s add a quality check with jsHint and Qhint (which is just a layer over jsHint). Link to the correct files in the head of the page. And add the following test to your tests.js file (demo.js contains the functions of previous steps):

  • qHint(“QHint code quality:”, “resources/demo.js”); Since we did such a fine job in coding those functions I get an “Okay” from jsHint.
JShint

JShint

Now you might get an error like this:

JShint error

JShint error

Which means you aren’t running the testsuite file from a server (IIS, or WAMP or anything like it). If the URL in your address bar starts with “file:///”, you’ll get punished with a broken test. So again I’d advise to put a condition around it, to ignore this test in case want to run the testsuite outside of a server environment.

Finally if you are really really serious about testing, you can run a code coverage test with JS-Coverage. A project called JSCover is being built on top of this, which checks even more metrics. Running JS-coverage involves executing and exe with some parameters, so I’ll refer to their manual instead of repeating it here: [link](http://siliconforks.com/jscoverage/manual.html](http://siliconforks.com/jscoverage/manual.html) .

Basically it takes your files and inserts instrumenting javascript in them and drops everything in a new directory. In this directory you’ll find a jscoverage.html. IMPORTANT just like jsHint you’ll need to run this file on a server. Once you open that, you’ll see a (very ugly) screen like this:

JS coverage

JS coverage

Here I already entered the url to the testsuite.html file.

IMPORTANT this should be the file inside the newly generated js-coverage directory as this is the one containing the instrumentation code.

If you click on the summary tab you’ll see something similar to this:

JS coverage summary

JS coverage summary

If you click on one of the files you’ll open up the source tab with some extra info:

JS coverage source

JS coverage source

That concludes this tutorial. I’ll include a zip-file which contains the code of this tutorial.

As a bonus I’ll add a second zip file containing a simple mastermind game, made TDD style with all these tool. You can find the tests in /js/testsuite.html I already generated the instrumented code which can be found in mastermind/jscoverage_src .

Mastermind screenshot

Mastermind screenshot

The idea behind making that mastermind game, besides the fact that it is fun, is to make a series of these tutorials. Next step will be some better layout and useabilty (yes, I’m talking about a fancy color picker and all that) and javascript templating with mustache.

List of links for more information:

  • [QHint] (https://github.com/gyoshev/qhint)
  • [JSHint] (http://www.jshint.com/)
  • [Benchmark] (http://benchmarkjs.com/)
  • [QUnit] (http://qunitjs.com/)
  • [JS-Coverage] (http://siliconforks.com/jscoverage/)

Zip files with all code for this tutorial: