Home > Javascript > Unit testing your Javascript: Just Do It

Unit testing your Javascript: Just Do It

December 15th, 2009

Nike’s slogan is the only one fit for the issue I’m about to raise in this article: Unit testing. Deep down, everyone knows the benefits from unit testing your code. Unit testing can give you that warm feeling when you go to bed, knowing that the changes you made, didn’t break previously working code. It makes you happy and it gives you confidence.

Yet, a lot of us (including me) don’t actually start unit testing our code. There are a number of reasons for that, but I’ll have none of that now. Because now, I’m going to show you just how easy it is to unit test your Javascript. I’ll be using QUnit, made by John Resig, creator of jQuery. Even though I’ve just begun using QUnit, the code I presented in my previous three blog posts, has really benefitted greatly from it.

Setting up the environment

To set up your basic unit testing environment, you won’t need to go through a lot of trouble. It’s as easy as creating an HTML page which loads the latest jQuery instance, QUnit instance and a stylesheet.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
                    "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Tag library Unit Tests</title>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <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>
    </head>
    <body>
        <h1 id="qunit-header">Tag library Unit Tests</h1>
        <h2 id="qunit-banner"></h2>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
    </body>
</html>

At this point, nothing much will be shown when you run the HTML file in your browser, as we still have to add our unit tests.

Empty QUnit page

Environment without tests

Adding Unit Tests

Now that you’ve seen how easy it is to set up your testing environment, it’s time to actually write a test. I will provide you with some samples from what I’ve used to test the Tag Javascript library. If you need an API, or you want to know all the possibilities, just head over to the QUnit website.

To keep my stuff organized, I try to reflect the regular structure as much as possible in my tests. This is what I mean:

Unit test file structure

File structure

All tests are in separate files, reflecting the original structure. The tests for /tag/tag.pubsub.js are in the file /tests/tag/tag.pubsub.js. I think it’s a good idea to keep your testing in modules. One big file, or even in the environment HTML file would also work. It’s just not that easy to work with if you have a lot of tests.

Since I’m all for the modular way, first thing we add is an indication that a new module is tested

$(function(){
    /**
     * Define the module:
     */
    module("Tag base class");
});

This goes in the file /tests/tag.js (see image above), and will be referenced in the HTML environment we set up as our first step:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
                    "http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="nl" lang="nl">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Tag library Unit Tests</title>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <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>
 
        <!-- add links to unit tests below: -->
        <script type="text/javascript" src="../tag.js"></script><!-- library being tested -->
        <script type="text/javascript" src="tag.js"></script><!-- test code for library -->
    </head>
    <body>
        <h1 id="qunit-header">Tag library Unit Tests</h1>
        <h2 id="qunit-banner"></h2>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
    </body>
</html>

In the environment, we first include the file containing the code to be tested. Next we include the file containing the tests for that code. You can add other files for each plugin you want to test.

Ok, our module is defined. The first test I’m adding is to verify if the file was loaded, and if the Tag namespace is defined in the window scope.

    /**
     * Define the module:
     */
    module("Tag base class");
 
    /**
     * Verify existence of the Tag object:
     */
    test("Tag", function() {
        expect(2); // optional
        equals(typeof window.Tag, "object", "Verifying existence of Tag object");
        equals(typeof window.Tag.constructor, "function", "Verifying constructor type");
    });

The tests to verify the existence of the Tag object are grouped inside a single test() case. Inside the test case, we can have as many assertions as we want. Here I have added 2 assertions: I verify if the type of window.Tag is an object, and if the constructor is a function. If both these assertions succeed, then that’s enough proof for me that the Tag base class is loaded and available on the page.

The basic premise of unit testing is that you test the smallest unit that makes up your code. In the case of the Tag library, the smallest unit will be a method from a plugin or the base class. So the next thing we’re obviously going to do, is test each of the methods inside the Tag object.

As an example, here are the assertions I’ve written to test the extend() functionality of Tag:

    /**
     * Verify working of extend method:
     */
    test("Tag.extend()",function() {
        // an assertion using ok()
        ok(typeof window.Tag.extend === "function","Method exists");
 
        // a dummy temporary plugin (also called a mock)
        var tmp = {
            _initCheck:false,
            init:function() {
                this._initCheck = true;
            }
        };
 
        // Verify reserved namespaces:
        var reserved = ["extFN", "extLocalFN"];
        $.each(reserved, function() {
            ok(!window.Tag.extend(this,tmp), "Namespace '" + this + "' cannot be overwritten");
        });
 
        // add test plugin
        ok(window.Tag.extend("test",tmp), "Namespace 'test' taken");
        ok(!window.Tag.extend("test",tmp), "Namespace 'test' cannot be taken twice");
        equals(typeof window.Tag.test, "object", "Tag.test plugin exists");
        equals(typeof window.Tag.fn.test, "object", "Test plugin added to FN");
 
        // verify if plugin is extended with extra functionality:
        $.each(reserved, function(index, ns) {
            var obj = window.Tag[ns];
 
            $.each(obj,function(key, value){
                same(window.Tag.test[key], obj[key], "Verified existence of '" + key + "'");
            });
        });
 
        // verify if init() was called:
        equals(true, window.Tag.test._initCheck, "Init was run");
    });

The extend() method does a lot of things, naturally, all these things need to be tested. It’s the only way to be sure that the method does what it promises to do. For complex methods, with lots of execution paths, it will become difficult to test everything. That’s a reason to keep your methods small, and work with subroutines: they’re just easier to test (and to understand half a year after you last touched that part of the code).

Unit testing AJAX calls

The Tag.core plugin has functionality to asynchronously load javascript files. The problem with AJAX is that it is asynchronous, while unit testing is synchronous. The tests will all be executed in order, no test will run in parallel with another test. Yet we still need to be able to test asynchronous calls. QUnit provides us with the asyncTest() method for this purpose. I haven’t used that method though, it’s possible to do it “manually”.

We just need to pause the chain of tests before our asynchronous test. Once that test is completed, we can resume the rest of the tests. This is basically just waiting for an asynchronous test to complete, so all things considered, we test an asynchronous test synchronously. Nothing will happen in parallel.

    /**
     * Verify working of loadJS method:
     */
    test("Tag.core.loadJS()",function() {
        // verify loading:
        window.Tag.core.loadJS("dummy.js");
        // when testing asynchronous functionality, we must temporarily stop the
        // running of the tests:
        stop();
        window.setTimeout(function(){
            equals(typeof window.dummy, "object", "Verifying existence of loaded dummy");
            // start the rest of the tests:
            start();
        },250);
    });

First, the chain of tests is paused via the stop() method. As soon as the test is done, the start() method will resume the normal chain of events. Simple but effective.

The asyncTest() method from QUnit does exactly the same as we have done here. It just executes the stop() method for you. You only need to indicate when to resume the tests.

Beware, an asynchronous test may only contain 1 asynchronous call. E.g. not executing 2 AJAX calls in 1 test but rather just 1. The test itself can contain multiple assertions.

    test("Tag.core.loadJS() multiple",function() {
        // verify loading multiple:
        window.Tag.core.loadJS(["dummy.js","dummy2.js"]);
        stop();
        window.setTimeout(function(){
            equals(typeof window.dummy, "object", "Verifying existence of loaded dummy");
            equals(typeof window.dummy2, "object", "Verifying existence of loaded second dummy");
            // start the rest of the tests:
            start();
        },250);
    });

The test results

While writing tests, you will want to see some results. Just save your files, and open the environment HTML file in your browser. The tests will be run, and you’ll be presented with the results:

Unit test results

Complete results, no errors

All failed assertions are marked in red. Any test can be clicked to see in a more detailed manner which assertions had what response:

Failed unit test detail

Suite with errors and detail

If you provided meaningful descriptions in your tests, it should be easy to see where an assertion failed.

Conclusion

Unit testing your Javascript functionality couldn’t be made easier. Getting started shouldn’t be the threshold, it’s as easy as creating one HTML file. Writing tests can be a bit of an adventure. At first you’ll probably make some mistakes, but that’s okay. The more you write tests, the more efficient you’ll make them. But you have to start somewhere. Just practice.

Remember, I can only show you the door. You have to walk through it. :)

All code from previous three blog posts now has a complete coverage from unit tests. You can still see the code at my Codaset repository. Please, feel free to download, fork, comment, …

Share and Enjoy:
  • DZone
  • del.icio.us
  • StumbleUpon
  • Digg
  • Ma.gnolia
  • Technorati
  • TwitThis

Tom Javascript

  1. December 16th, 2009 at 21:34 | #1

    Brillante!!! excelente post

  2. December 21st, 2009 at 10:05 | #2

    Excelent article

  3. January 22nd, 2010 at 12:36 | #3

    I just started using Qunit. I was searching for an article I read the other day and came across yours. The set timeout thing is cool, but not when you have a lot of tests and the time outs add up.

    Check out this post for a better way to test asynchronous code

    http://www.onenaught.com/posts/85/turn-your-jquery-code-into-a-richer-unit-testable-plugin

  4. January 22nd, 2010 at 12:50 | #4

    Very interesting approach. I’ll have to read the post again, because after a first quick read, I didn’t fully understand how the concept works.

    Thanks!

  1. December 16th, 2009 at 19:48 | #1
  2. December 17th, 2009 at 14:03 | #2
  3. December 17th, 2009 at 22:27 | #3
  4. December 25th, 2009 at 18:33 | #4
  5. January 4th, 2010 at 01:26 | #5