At the Forge

Testing JavaScript

Reuven M. Lerner

Issue #191, March 2010

A look at Screw.Unit, a framework for JavaScript testing.

In mid-November 2009, at a meeting of my research group in Chicago, I proudly unveiled the most recent beta of the software I'm writing for my PhD dissertation, a Web application (written in Ruby on Rails) that promotes collaboration among students and scientists. I was pretty confident the testing would not reveal too many technical problems, in part because I had used Cucumber and rcov to ensure a high degree of test coverage. True, my application uses some AJAX, which means there are certain things Cucumber cannot test. But, given how localized such functions are, and the fact that I used and tested them myself on a day-to-day basis, how much did it matter?

The good news is that for the most part, the beta test went quite well. There were a few problems to fix, and I started to come up with a plan to get to them. What bothered me most was not that bugs existed, but rather that the bugs were all related to JavaScript and AJAX. In other words, the high level of test coverage that I had achieved was good, but it was not sufficient, because it looked only at my Ruby code and not at the equally important JavaScript code in my application.

This was not the first time I had encountered issues with JavaScript testing. A project I worked on through much of 2009 used a great deal of JavaScript, and we tried to test it in a number of ways, none of which were particularly satisfactory.

So, I was pleasantly surprised to discover I'm not the only Web developer who has been trying to improve test coverage for Web applications that include a great deal of JavaScript. Indeed, currently a number of frameworks and libraries are available for JavaScript testing—some of which are specific to a particular JavaScript framework, some of which are plugins for Ruby on Rails (or other Web application frameworks) and still others that are fairly flexible and agnostic.

This month, I look at Screw.Unit, a framework for JavaScript testing I have begun to use in my own work. Even if you don't use Screw.Unit specifically, modern Web developers constantly must consider ways to write testable code, not only in their server-side language of choice, but also in JavaScript. JavaScript plays a central role in modern Web applications, and failing to test it thoroughly can lead to unforeseen problems, as I saw myself.

Downloading and Installing Screw.Unit

Screw.Unit originally was written by Nick Kallen (of Pivotal Labs) and distributed as open source on GitHub. A number of forked versions exist, and you might need to poke around to find one that is sufficiently mainstream and modern for your needs. I have been using Kallen's original version and rely on it for this article's examples. GitHub provides a number of methods for downloading software, but the easiest is just to “clone” the existing Git repository, with:

git clone git://github.com/nkallen/screw-unit.git

Inside the screw-unit directory, you will find a number of JavaScript libraries and CSS files, all of which are there to assist you when running JavaScript tests.

The basic idea of Screw.Unit is that you introduce a set of related tests with describe() and then each individual test with it(). The second parameter to it() is a function that invokes one or more assertions, using the defined expect() function.

Thus, let's assume you have a function defined that multiplies its parameter by 3:

function triple(i) {
    return i * 3;
}

You can test it in Screw.Unit with the following:

describe("Triple should triple", function() {
    it("returns 6 for 2", function() {
        expect(triple(2)).to(equal, 6);
    });
});

Notice the three separate levels of functions that are involved here:

  • describe introduces a block of common specifications.

  • it describes and introduces a single specification.

  • expect executes one test for that specification.

In order to run these tests, you need to wrap the entire describe block inside an anonymous function, passed as the first parameter to Screw.Unit():

Screw.Unit(function() {
    describe("Triple should triple", function() {
        it("returns 6 for 2", function() {
          expect(triple(2)).to(equal, 6);
        });
    });
});

Finally, you need to pull in a bunch of JavaScript libraries that not only define Screw.Unit, but also the objects and functions on which it relies. The final version is shown in Listing 1, triple.html. Notice that while you are testing JavaScript, Screw.Unit assumes you are doing so within an HTML file. That allows you not only to load an (unfortunately long) list of JavaScript libraries, but also the CSS file that is used within Screw.Unit to display test results.

The test is passed when Screw.Unit() is executed. If it works well, the body of the HTML document is modified accordingly, using CSS classes (defined in screw.css) that give the traditional green (for passing) or red (for failing) report on the tests you performed.

I'm going to add two more tests, one that I know will pass, which uses the not_equal test modifier. The other test will fail, so you can examine what it looks like when one does. If all goes well, you should see two green bars and one reddish-brown bar, the last of which indicates failure. The test itself looks like this:


<script type="text/javascript">
     Screw.Unit(function() {

       describe("Triple should triple", function() {
         it("returns 6 for 2", function() {
           expect(triple(2)).to(equal, 6);
         });

         it("does not return 100 for 2", function() {
           expect(triple(2)).to_not(equal, 100);
         });

         it("does return 100 for 2 -- fail!", function() {
           expect(triple(2)).to(equal, 100);
         });
       });
     });
</script>

As you can see, you can include as many it statements inside a describe block as you need. Over time, you will see your spec grow to contain more and more descriptions, it statements and expect statements.

Checking the DOM

Testing JavaScript functions is certainly a good thing to do. However, one of the primary uses of JavaScript is to modify the DOM—the document object model that provides a handle onto the contents of an HTML page. Using the DOM, you can view or modify the page, both in its tags and in its content.

Thus, DOM manipulations are a primary target for JavaScript tests. You want to be able to say that when a particular piece of HTML is clicked on, another piece of HTML should appear.

Now, some of the documentation for Screw.Unit will tell you that you can (and should) use a click() method to simulate clicking on an element of your page. I not only found the click() method to be unreliable, but also was persuaded by a posting on the Screw.Unit mailing list to put my text-hiding code in a separate function, which can then be called from within the click() handler for the paragraph and also from the test within an it block. This not only worked, but also encouraged a style that is more readable and easily workable, in my opinion.

The full file, clickview.html, is in Listing 2. The idea is that the document contains a paragraph:


<p id="hideme">Click to hide</p>

You then attach a click() event handler to the paragraph, invoking a function when the paragraph is clicked on:

function hide_paragraph() {
        $("#hideme").hide();
}

$(document).ready(function() {
    $('#hideme').click(function() {
        hide_paragraph();
    });
});

Finally, you set up a Screw.Unit() test block, as follows:

Screw.Unit(function() {

  describe("Paragraph", function() {

    it("should be hidden when clicked", function() {
      hide_paragraph();
      expect($('#hideme').is(':hidden')).to(equal, true);

    });
  });
});

When you load the page, Screw.Unit first invokes the function hide_paragraph(), which has the same effect that clicking on it would have. Then it checks to make sure, using a pseudo-class (:hidden) to identify hidden text. If no text with the ID “hideme” is currently hidden, jQuery returns an empty list, and the assertion fails.

The fact that everything in Screw.Unit, as in jQuery, is done using CSS selectors makes it easy and fast to work with. It would seem that there are people doing TDD (test-driven development) and BDD (behavior-driven development) using Screw.Unit; although I don't count myself among those, I do see myself using this sort of testing in the future, if only to avoid egg on my face among my software users. Besides, testing JavaScript in this way, at least to my mind, gives me a feeling of working on serious software, rather than one or more basic hacks.

I should note that the style in which I presented Screw.Unit in this column is a concise, but not idiomatic way that most users will put it into practice. Screw.Unit users typically separate tests into multiple files, allowing them not only to define custom test matchers, but also to have an entire library of tests, rather than just one file. Once you get started with Screw.Unit, I'm sure you will find a style that suits your needs, without running too much against the grain of the system's expected use.

Conclusion

Screw.Unit is an easy-to-understand, easy-to-use framework for testing your JavaScript code. It is not the only test system of its kind, but the fact that its syntax is reminiscent of RSpec makes it easier for people like me, who like and use RSpec, to start using it quickly. RSpec advocates also will want me to point out that Screw.Unit offers JavaScript developers the same sort of BDD that characterizes RSpec and Cucumber, focusing on the results that the user sees, rather than the internal workings.

If you have never tested your JavaScript before, there's no time like the present to begin! If nothing else, you want to be sure that clicking on various parts of your HTML page does not lead to errors.

Reuven M. Lerner, a longtime Web/database developer and consultant, is a PhD candidate in learning sciences at Northwestern University, studying on-line learning communities. He recently returned (with his wife and three children) to their home in Modi'in, Israel, after four years in the Chicago area.