Advertisement

How to Test your JavaScript Code with QUnit

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

QUnit, developed by the jQuery team, is a great framework for unit testing your JavaScript. In this tutorial, I'll introduce what QUnit specifically is, and why you should care about rigorously testing your code.

What is QUnit

QUnit is a powerful JavaScript unit testing framework that helps you to debug code. It's written by members of the jQuery team, and is the official test suite for jQuery. But QUnit is general enough to test any regular JavaScript code, and it's even able to test server-side JavaScript via some JavaScript engine like Rhino or V8.

If you're unfamiliar with the idea of "unit testing", don't worry. It's not too difficult to understand:

In computer programming, unit testing is a software verification and validation method in which a programmer tests if individual units of source code are fit for use. A unit is the smallest testable part of an application. In procedural programming a unit may be an individual function or procedure.

This is quoted from Wikipedia. Simply put, you write tests for each functionality of your code, and if all of these tests are passed, you can be sure that the code will be bug-free (mostly, depends on how thorough your tests are).

Why You Should Test Your Code

If you haven't written any unit tests before, you probably just apply your code to a website directly, click for a while to see if any problem occurs, and try to fix it as you spot one. There are many problems with this method.

First, it's very tedious. Clicking actually is not an easy job, because you have to make sure everything is clicked and it's very likely you'll miss one thing or two. Second, everything you did for testing is not reusable, which means it's not easy to find regressions. What is a regression? Imagine that you wrote some code and tested it, fixed all the bugs you found, and published it. Then, a user sends some feedback about new bugs, and requests some new features. You go back to the code, fix these new bugs and add these new features. What might happen next is that some of the old bugs come up again, which are called "regressions." See, now you have to do the clicking again, and chances are you won't find these old bugs again; even if you do, it'll take a while before you figure out that the problem is caused by regressions. With unit testing, you write tests to find bugs, and once the code is modified, you filter it through the tests again. If a regression appears, some tests will definitely be failed, and you can easily spot them, knowing which part of the code contains the bug. Since you know what you have just modified, it can easily be fixed.

Another advantage of unit testing is especially for web development: it eases the testing of cross-browser compatibility. Just run your tests on different browsers, and if a problem occurs on one browser, you fix it and run these tests again, making sure it doesn't introduce regression on other browsers. You can be sure that all of the target browsers are supported, once they all pass the tests.

I'd like to mention one of John Resig's projects: TestSwarm. It takes JavaScript unit testing to a new level, by making it distributed. It's a website that contains many tests, anyone can go there, run some of the tests, then return the result back to server. In this way, code can be tested on different browsers and even different platforms really quickly.

How to Write Unit Tests with QUnit

So how do you write unit tests with QUnit exactly? First, you need to set up a testing environment:

<!DOCTYPE html>
<html>
<head>
	<title>QUnit Test Suite</title>
	<link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen">
	<script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
	<!-- Your project file goes here -->
	<script type="text/javascript" src="myProject.js"></script>
	<!-- Your tests file goes here -->
	<script type="text/javascript" src="myTests.js"></script>
</head>
<body>
	<h1 id="qunit-header">QUnit Test Suite</h1>
	<h2 id="qunit-banner"></h2>
	<div id="qunit-testrunner-toolbar"></div>
	<h2 id="qunit-userAgent"></h2>
	<ol id="qunit-tests"></ol>
</body>
</html>

As you can see, a hosted version of QUnit framework is used here.

The code that is going to be tested should be put into myProject.js, and your tests should be inserted into myTests.js. To run these tests, simply open this HTML file in a browser. Now it's time to write some tests.

The building blocks of unit tests are assertions.

An assertion is a statement that predicts the returning result of your code. If the prediction is false, the assertion has failed, and you know that something has gone wrong.

To run assertions, you should put them into a test case:

// Let's test this function
function isEven(val) {
	return val % 2 === 0;
}

test('isEven()', function() {
	ok(isEven(0), 'Zero is an even number');
	ok(isEven(2), 'So is two');
	ok(isEven(-4), 'So is negative four');
	ok(!isEven(1), 'One is not an even number');
	ok(!isEven(-7), 'Neither is negative seven');
})

Here we defined a function, isEven, which detects whether a number is even, and we want to test this function to make sure it doesn't return wrong answers.

We first call test(), which constructs a test case; the first parameter is a string that will be displayed in the result, and the second parameter is a callback function that contains our assertions. This callback function will get called once QUnit is run.

We wrote five assertions, all of which are boolean. A boolean assertion expects its first parameter to be true. The second parameter is also a message that will be displayed in the result.

Here is what you get, once you run the test:

a test for isEven()

Since all these assertions have successfully passed, we can be pretty sure that isEven() will work as expected.

Let's see what happens if an assertion has failed.

// Let's test this function
function isEven(val) {
	return val % 2 === 0;
}

test('isEven()', function() {
	ok(isEven(0), 'Zero is an even number');
	ok(isEven(2), 'So is two');
	ok(isEven(-4), 'So is negative four');
	ok(!isEven(1), 'One is not an even number');
	ok(!isEven(-7), 'Neither does negative seven');

	// Fails
	ok(isEven(3), 'Three is an even number');
})

Here is the result:

a test contains failed assertion for isEven()

The assertion has failed because we deliberately wrote it wrong, but in your own project, if the test doesn't pass, and all assertion are correct, you know a bug has been found.

More Assertions

ok() is not the only assertion that QUnit provides. There are other kinds of assertions that are useful when testing your project:

Comparison Assertion

The comparison assertion, equals(), expects its first parameter (which is the actual value) is equal to its second parameter (which is the expected value). It's similar to ok(), but outputs both actual and expected values, making debugging much easier. Like ok(), it takes an optional third parameter as a message to be displayed.

So instead of:

test('assertions', function() {
	ok( 1 == 1, 'one equals one');
})
a boolean assertion

You should write:

test('assertions', function() {
	equals( 1, 1, 'one equals one');
})
a comparison assertion

Notice the last "1", which is the comparing value.

And if the values are not equal:

test('assertions', function() {
	equals( 2, 1, 'one equals one');
})
a failed comparison assertion

It gives a lot more information, making life a lot easier.

The comparison assertion uses "==" to compare its parameters, so it doesn't handle array or object comparison:

test('test', function() {
	equals( {}, {}, 'fails, these are different objects');
	equals( {a: 1}, {a: 1} , 'fails');
	equals( [], [], 'fails, there are different arrays');
	equals( [1], [1], 'fails');
})

In order to test this kind of equality, QUnit provides another kind assertion: identical assertion.

Identical Assertion

Identical assertion, same(), expects the same parameters as equals(), but it's a deep recursive comparison assertion that works not only on primitive types, but also arrays and objects. Assertions, in the previous example, will all pass if you change them to identical assertions:

test('test', function() {
	same( {}, {}, 'passes, objects have the same content');
	same( {a: 1}, {a: 1} , 'passes');
	same( [], [], 'passes, arrays have the same content');
	same( [1], [1], 'passes');
})

Notice that same() uses '===' to do comparison when possible, so it'll come in handy when comparing special values:

test('test', function() {
	equals( 0, false, 'true');
	same( 0, false, 'false');
	equals( null, undefined, 'true');
	same( null, undefined, 'false');
})

Structure Your Assertions

Putting all assertions in a single test case is a really bad idea, because it's very hard to maintain, and doesn't return a clean result. What you should do is to structure them, put them into different test cases, each aiming for a single functionality.

You can even organize test cases into different modules by calling the module function:

module('Module A');
test('a test', function() {});
test('an another test', function() {});

module('Module B');
test('a test', function() {});
test('an another test', function() {});
structure assertions

Asynchronous Test

In previous examples, all assertions are called synchronously, which means they run one after another. In the real world, there are also many asynchronous functions, such as ajax calls or functions called by setTimeout() and setInterval(). How can we test these kinds of functions? QUnit provides a special kind of test case called "asynchronous test," which is dedicated to asynchronous testing:

Let's first try to write it in a regular way:

test('asynchronous test', function() {
	setTimeout(function() {
		ok(true);
	}, 100)
})
an incorrent example of asychronous test

See? It's as if we didn't write any assertion. This is because the assertion ran asynchronously, by the time it got called, the test case had already finished.

Here is the correct version:

test('asynchronous test', function() {
	// Pause the test first
	stop();
	
	setTimeout(function() {
		ok(true);

		// After the assertion has been called,
		// continue the test
		start();
	}, 100)
})
a correct example of asychronous test

Here, we use stop() to pause the test case, and after the assertion has been called, we use start() to continue.

Calling stop() immediately after calling test() is quite common; so QUnit provides a shortcut: asyncTest(). You can rewrite the previous example like this:

asyncTest('asynchronous test', function() {
	// The test is automatically paused
	
	setTimeout(function() {
		ok(true);

		// After the assertion has been called,
		// continue the test
		start();
	}, 100)
})

There is one thing to watch out for: setTimeout() will always call its callback function, but what if it's a custom function (e.g, an ajax call). How can you be sure the callback function will be called? And if the callback is not called, start() won't be called, and the whole unit testing will hang:

unit testing hangs

So here is what you do:

// A custom function
function ajax(successCallback) {
	$.ajax({
		url: 'server.php',
		success: successCallback
	});
}

test('asynchronous test', function() {
	// Pause the test, and fail it if start() isn't called after one second
	stop(1000);
	
	ajax(function() {
		// ...asynchronous assertions

		start();
	})
})

You pass a timeout to stop(), which tells QUnit, "if start() isn't called after that timeout, you should fail this test." You can be sure that the whole testing won't hang and you'll be notified if something goes wrong.

How about multiple asynchronous functions? Where do you put the start()? You put it in setTimeout():

// A custom function
function ajax(successCallback) {
	$.ajax({
		url: 'server.php',
		success: successCallback
	});
}

test('asynchronous test', function() {
	// Pause the test
	stop();
	
	ajax(function() {
		// ...asynchronous assertions
	})

	ajax(function() {
		// ...asynchronous assertions
	})

	setTimeout(function() {
		start();
	}, 2000);
})

The timeout should be reasonably long enough to allow both callbacks to be called before the test continues. But what if one of the callback isn't called? How can you know that? This is where expect() comes in:

// A custom function
function ajax(successCallback) {
	$.ajax({
		url: 'server.php',
		success: successCallback
	});
}

test('asynchronous test', function() {
	// Pause the test
	stop();

	// Tell QUnit that you expect three assertions to run
	expect(3);

	ajax(function() {
		ok(true);
	})

	ajax(function() {
		ok(true);
		ok(true);
	})

	setTimeout(function() {
		start();
	}, 2000);
})

You pass in a number to expect() to tell QUnit that you expect X many assertions to run, if one of the assertion isn't called, the number won't match, and you'll be notified that something went wrong.

There is also a shortcut for expect(): you just pass the number as the second parameter to test() or asyncTest():

// A custom function
function ajax(successCallback) {
	$.ajax({
		url: 'server.php',
		success: successCallback
	});
}

// Tell QUnit that you expect three assertion to run
test('asynchronous test', 3, function() {
	// Pause the test
	stop();

	ajax(function() {
		ok(true);
	})

	ajax(function() {
		ok(true);
		ok(true);
	})

	setTimeout(function() {
		start();
	}, 2000);
})

Conclusion

That's all you need to know to get started witih QUnit. Unit testing is a great method to test your code before publishing it. If you haven't written any unit tests before, it's time to get started! Thanks for reading!

Advertisement