Getting Started with QUnit in jQuery

QUnit was born May 2008, out of the jQuery project’s own testrunner. As it evolved, it was spun off as a separate project, gaining a name, documentation, and eventually being completely decoupled from jQuery.

If you’re comfortable with the concepts of unit testing in general, getting up and running with QUnit is relatively straightforward. The following section outlines the key structure of QUnit testing and illustrates the basic assertions you’ll use to test your code.

1. The QUnit Hello World Using equal

The following file shows a bare-bones QUnit test. It contains a copy of jQuery, a copy of the QUnit script file, a copy of the QUnit CSS file, and some script and markup that make up the body of the test itself.

The script block uses the QUnit method test.test does what the name implies — it sets up a test to run. Here it accepts a pair of arguments, a name, “Hello World,” and a function that actually represents the test. The function argument itself uses the QUnit function equal. In unit testing terminology, equal is referred to as an assertion. An assertion indicates what you expect the code to do. As the name implies, equal tests equivalence. It accepts three arguments: an actual variable to be tested, the asserted expected value, and a message to display alongside the assertion.

The markup contains several QUnit-specific HTML elements:

#qunit-header contains the name of the test suite.

#qunit-banner shows up red with a failing test and green if all tests pass.

#qunit-userAgent displays the navigator.userAgent property.

#qunit-tests is a container for the test results.

#qunit-fixture is where you place and manipulate your test markup. You learn more about #qunit-fixture in a later section.

<!DOCTYPE html>

<html>

<head>

<script

src=”http://code.jquery.com/jquery-1.7.1.js”>

</script>

<link rel=”stylesheet”

href=”http://code.jquery.com/qunit/git/qunit.css”

type=”text/css”

media=”screen” />

<script

src=”http://code.jquery.com/qunit/git/qunit.js”></script>

<script>

$( function(){

test(“Hello World”, function() {

var message = “hello world”;

equal( message,“hello world”,

“We expect the message to match”

);

});

});</script>

</head>

<body>

<h1 id=”qunit-header”>QUnit example</h1>

<h2 id=”qunit-banner”></h2>

<div id=”qunit-testrunner-toolbar”></div>

<h2 id=”qunit-userAgent”></h2>

<ol id=”qunit-tests”>

</ol>

<div id=”qunit-fixture”>Test Markup</div>

</body>

</html>

Code snippet is from simple-test.html

Running that single test produces the output shown in Figure 14-1.

If you see that for all of your tests, it’s time to go to lunch. You’ve earned it.

2. The Differences between xUnit and QUnit

Although the name might imply otherwise, it should be noted that QUnit isn’t part of the xUnit family.

xUnit is the name given to a family of testing frameworks that trace their roots back to a design by Kent Beck, outlined in the paper “Simple Smalltalk Testing: With Patterns” (http://www .xprogramming.com/testfram.htm). Originally implemented for Smalltalk as SUnit, this design has gone on to be ported to several other languages including Java (JUnit), C++ (CppUnit), and .NET (NUnit).

Though QUnit shares basic concepts like assertions, significant differences exist between the xUnit design and QUnit. For example, in the previous example, you saw two arguments for equal:actual and expected. In the xUnit design, the order of those arguments is reversed.

Say xUnit and QUnit also have completely different assertion names. Looking again at the previous example, equal is analogous to the xUnit assertion assertEquals. They serve the same purpose, just with slightly different syntax. Another difference is that some assertions included in xUnit are missing from QUnit.

None of this will matter if you’re not familiar with the xUnit family, of course. If you are, it is worth keeping your eye on the differences so you don’t get tripped up moving forward.

Now that you’ve gotten that potential gotcha out of the way, you can continue your examination of QUnit.

3. A Failing QUnit Test

You’ve seen a passing test; now it’s time to look at a failing test. With the same markup as the previous example, you can run the following test with an equal assertion:

test(“Hello World”, function() {

var message = “Goodbye cruel world”;

equal( message, “hello world”, We expect the message to match ” );

}).

Code snippet is from failing-test.html

As you can see in Figure 14-2, a failing test provides much more interesting information than a passing test. For starters, the message defined as the third test argument is displayed alongside the expected and actual values of the variable being tested, in this case “hello world” and “Goodbye cruel world,” respectively. Additionally, it does a diff of the variables and a pointer to the line that generated the error.

4. Testing for Truthiness with ok

In some ways, this would have made a better “Hello World” example, because it’s the most basic assertion. ok accepts two arguments: a Boolean state to test and a message to display in the case of a failing test. It passes if the state is truthy. The following code example shows ok in action. It tests whether the return value of a function is an array using the jQuery method $.isArray.

test(“Truthiness is golden”, function!) {

var val = function(){

var arr = [1,2,3,4,5]

//dostuff

return arr;

}

ok( $.isArray(val()), “We’re expecting an array as a return value” );

});

Code snippet is from ok.html

5. Setting Expectations

One good habit to get into is to set expectations for your tests. Basically, you tell QUnit, “Expect to see x assertions.” If QUnit doesn’t see exactly x assertions, the whole test will fail. This is true even if the assertions that did run all passed.

Tests might not run for a variety of reasons, from something as simple as a typo to more complicated issues relating to application flow. You see the prime example of why expect is important when you learn about asynchronous testing in a later section.

You set expectations in QUnit in two ways. The first is by calling the expect method within the test body. The following example shows expect in use. Two assertions are expected but only one is present, so the whole test fails.

test(“Hello World”, function() {

expect(2)

var message = “hello world”; equal( message, “hello world”, “ We expect the message to match.” ); 

});

Code snippet is from expect.html

Figure 14-3 illustrates the result of this test. The test fails even though all of the assertions that did run passed.

In addition to the expect method, it’s possible to set expectations with an optional second argument to the test method:

test(“Hello World”, 2, function() {

var message = “hello world”;

equal( message, “hello world”, “We expect the message to match );

});

Code snippet is from expect.html

While both are valid options, and you’ll see both in use in large test suites, the explicit call to expect within the test body is much cleaner and easier to read. I prefer it over the second argument to test.

6. Additional Assertions

Table 14-1 lists all available assertions in QUnit. For the most part, they all have the same straightforward syntax you’ve already seen with the other assertions in this section. They either provide negative assertions like notEqual (the opposite of equal) or offer more/different levels of precision like deepEqual and strictEqual. Familiarize yourself with this list; it will help when you’re writing your own tests and need that extra bit of precision.

7. Testing DOM Elements

If you’re doing any DOM manipulations (and because you’re reading a jQuery book, you probably are), you’ll want to be able to test the output. QUnit provides an interface to do just that. To test DOM elements, simply set your markup in #qunit-fixture, make your DOM magic, and then use assertions to test the state of the DOM. QUnit will reset qunit-fixture after each test.

A simple example is shown in the following code:

<!DOCTYPE html>

<html>

<head>

<script src=”http://code.jquery.com/jquery-1.7.1.js”></script>

<link rel=”stylesheet” href=”http://code.jquery.com/qunit/git/qunit.css” />

<script type=”text/javascript” src=”http://code.jquery.com/qunit/git/qunit.js”></script>

<script>

$(function(){

test(“DOM”, function() {

expect(3);

$test = $(“#dom-test”);

$test.css(“width”,”200px”).text(“Testing the DOM”).show();

equal( $test.css(“width”), “200px”, “200px!” );

equal( $test.text(), “Testing the DOM”, “We’re testing the DOM” );

equal( $test.css(“display”), “block”, “display:block” );

});

});

</script>

</head>

<body>

<h1 id=”qunit-header”>QUnit example</h1>

<h2 id=”qunit-banner”></h2>

<div id=”qunit-testrunner-toolbar”></div>

<h2 id=”qunit-userAgent”></h2>

<ol id=”qunit-tests”>

</ol>

<div id=”qunit-fixture”><div id=”dom-test” style=”display:none”></div></div>

</body>

</html>

Code snippet is from dom-test.html

The jQuery project contains many more examples. With the amount of DOM manipulation that goes on with jQuery, the project needs to ensure that it’s performing reliably. Therefore, hundreds of assertions exist to test the state of the DOM.

8. Using noglobals and notrycatch

Two additional options to note have been staring you in the face throughout all the examples you’ve seen so far. They appear as options on every test as two checkboxes, noglobals and notrycatch, as shown in Figure 14-4.

Both set file-level flags on the test that will change the behavior of all tests performed on the page.

The noglobals flag will fail a test if a new global variable is introduced by the running code. If you’re coding in ES5 strict mode or are running a code linter like JSHint (http://www.jshint .com/), this shouldn’t be a problem for you. It’s still a useful check to have for those situations where globals might sneak through to the testing phase.

This simple example shows a test that will trigger the noglobals flag and the ensuing error message in the testrunner, as shown in Figure 14-5:

test(“Hello World”, function() {

message = “hello world”;

equal( message, “hello world”, “We expect the message to be ‘hello world'”);

});

Setting notrycatch will run QUnit without a surrounding try-catch block. This will actually stop the testrunner in its tracks but will allow for deeper debugging of an issue. The following example shows this option in action:

test(“No Try-Catch”, function!) {

// $$ will cause an exception

var test = $$(this).isGonnaExplode();

equal( test , “Return Value of Doom”, “We’re looking for a cool return value” );

});

Figure 14-6 shows the exception in Firebug. Without notrycatch, the Firebug console would be empty and the error would be logged in the testrunner.

In addition to running these tests by checking some checkboxes, you can also run them by appending a querystring parameter to the URL. To run tests with noglobals, for example, you would run this test list:

http://example.com/tests/tests.html?noglobals=true

This is a handy feature if you’re running multiple tests on a large application.

9. Organizing Tests into Modules

One key feature of QUnit is the ability to organize tests into modules. Just like the code you’re testing, the ability to organize code into separate modules allows for greater flexibility when running tests.

Grouping multiple tests that target a specific method or feature is a prime example of module usage. Looking at the test suite for the jQuery core in Figure 14-7, you can see that the project is broken into 17 separate modules.

Additionally, you can filter modules at run time (more on that in a second) and set up specific environment variables for the module’s test to run under.

The following code shows a simplified module, complete with two additional new features that empower the custom environment: setup and teardown. These are methods that are run before and after the tests in the module, and allow for great flexibility in terms of setting up the test environment for similar methods.

The following example sets up a variable to test throughout the module, arr (an array). It also stores a value in a variable, control, to test against later, on the off chance that something crazy happens to arr during the . Inside the module itself are two tests. One contains two ok assertions and the other contains deepEqual. These are all testing different pieces of the Array.

module(“fun with arrays”, {

setup: function() {

this.arr = [1,2,3,4,5];

this.control = this.arr.length;

},

teardown: function() {

equal(this.arr.length, this.control, “Just Checking to see if it’s still an Array”);

}

});

test(“Truthiness is golden”, function() {

expect(3);

var bool = $.isArray(this.arr);

ok( bool, “We’re expecting an array” );

bool = this.arr.indexOf(5);

ok(bool, “We expect 5 to be in the Array”);

});

test(“Getting Deep”, function() {

expect(2);

deepEqual(this.arr, [1,2,3,4,5]);

});

Code snippet is from module.html

Running these tests produces the output shown in Figure 14-8. As you can see, the tests are nested under the “fun with arrays” module name.

This organizational approach allows you to test specific site sections or features without having to run an entire test suite. This is a useful feature if you’re on a large project with many associated tests.

10. Filtering Tests with URL Parameters

Similar to the way you can set the noglobals and notrycatch flags with query parameters, you can also filter tests. Simply add ?filter=filtername to the URL and QUnit will only run tests whose names contain filtername. This can be based on module names or test names and allows you to easily target specific features when doing tests.

The use of this feature is made apparent when bug fixing the jQuery core. Although you’re required to run the full test suite before issuing a pull request, you don’t want to test the entire library when you’re testing against your feature-specific fix.

With filtering, you can only test against the pertinent areas while doing development and then you only need to run the full test once, before you send off your (presumably heroic) bug fix.

Source: Otero Cesar, Rob Larsen (2012), Professional jQuery, John Wiley & Sons, Inc

Leave a Reply

Your email address will not be published. Required fields are marked *