Easy JavaScript Unit Test Integration

At my current client I’ve been building a rather complex rich client app using ExtJs. The team has written a lot of JavaScript code, and has been writing a lot of JavaScript unit tests as well.

We’ve gone through several unit testing tools along the way trying to solve some logistical problems. We started with JSUnit, added Selenium, switched from JSUnitt to envjs, and are now using a hybrid approach I’ll describe in detail.

Some of the issues we wanted to address with our testing tools were:

  • How easy is it to run the tests?
  • How easy is it to debug the tests?
  • How easy is it to integrate the tests into our existing continuous integration environment

Most of the tools we used easily met the first two logistical issues, but the last one was not as simple.

The following describes an approach that meets all of the above list (after the jump).

Here is a very simple JavaScript object to test and illustrate the example:

  1. var swineFluIndicator = {
  2.   hasSwineFlu: function(sample) {
  3.     return !!sample.oink ? true : false;
  4.   }
  5. };

If the sample has a property “oink”, the sample is infected with swine flu. Simple.

To test our code in development, we used the Screw.Unit test framework.

We went with Screw.Unit for several reasons:

  • Simple to implement- It’s just a JavaScript library
  • It generates a simple DOM output, which makes integration easy – I’ll demonstrate later.
  • It’s easy to customize, and the framework does not pollute the global namespace at all.
  • It’s simple to run and debug.

Here is an example Screw.Unit test of the sample code:

  1. Screw.Unit(function() {
  2.   describe("test whether a given sample has swine flu", function() {
  3.    it("clean sample", function() {
  4.      var swineFluIndicator  = new SwineFluIndicator();
  5.      var testSample = {
  6.        value : '123'
  7.      };
  8.       expect(swineFluIndicator.hasSwineFlu(testSample)).to(be_false);
  9.     });
  10.   });
  11. });

Screw.Unit tests are executed in the browser (so debugging with Firebug is easy, and produce a very simple DOM that’s simple to read:
Screw.Unit Runner

Writing JavaScript unit tests is pretty easy; now it’s time to integrate them. HtmlUnit have released 2 new versions in 2009 so far that offer unprecedented DOM support via Rhino. With HtmlUnit, our test consists of rendering the page and interpreting the simple DOM results.

Set up the clients and load the page:

  1. WebClient firefoxClient = new WebClient(BrowserVersion.FIREFOX_2);
  2. WebClient ieClient = new WebClient(BrowserVersion.INTERNET_EXPLORER_6);
  3.  
  4. firefoxClient.setThrowExceptionOnScriptError(true);
  5. ieClient.setThrowExceptionOnScriptError(true);
  6. HtmlPage htmlPage = (HtmlPage) client.getPage(testFile.toURL());

and provide a means of interpreting the page results:

  1. protected void interpretTestResults(final HtmlPage htmlPage) {
  2.         String header = "";
  3.         String heading = "";
  4.         String paragraph = "";
  5.         // returns a list of tests
  6.         List testDivs = (List) htmlPage
  7.             .getByXPath("//*[contains(@class,'describe focused')]");
  8.  
  9.         for (HtmlListItem htmlListItem : testDivs) {
  10.             List heading1List = (List) htmlListItem.getByXPath("./h1");
  11.             header = heading1List.get(0).getTextContent();
  12.             // returns a list of its divisions; if there are nested
  13.             // describes, there will be multiple its
  14.             List test = (List) htmlListItem
  15.                 .getByXPath(".//*[contains(@class,'its')]");
  16.             for (HtmlUnorderedList htmlUnorderedList : test) {
  17.                 // if there are no child nodes, this is part of a nested
  18.                 // describe
  19.                 if (htmlUnorderedList.hasChildNodes()) {
  20.                     List failedItemList = (List) htmlUnorderedList
  21.                         .getByXPath(".//*[contains(@class,'it enqueued failed')]");
  22.                     if (failedItemList.size() == 0) {
  23.                         // successful
  24.                     } else {
  25.                         for (HtmlListItem failedItem : failedItemList) {
  26.                             List describedList = (List) failedItem
  27.                                 .getByXPath("./h2");
  28.                             Assert.assertTrue(describedList.size() == 1);
  29.                             heading = describedList.get(0).getTextContent();
  30.                             List errorMessageList = (List) failedItem
  31.                                 .getByXPath("./*[contains(@class, 'error')]");
  32.                             for (HtmlParagraph htmlParagraph : errorMessageList) {
  33.                                 paragraph += htmlParagraph.getTextContent();
  34.                             }
  35.                         }
  36.                         Assert.fail("Header: " + header + " heading: " + heading + " paragraph: "
  37.                             + paragraph);
  38.                     }
  39.                 }
  40.             }
  41.         }
  42.     }

This method takes an HtmlUnit HtmlPage and, using xpath expressions, iterates over the test result DIV tags generated by Screw.Unit. It ignores tests that passed, and focuses on the failed tests. For failed tests it gets the provided information about the test (the it and describes strings), so that we’ll know what test failed and why.

Lastly, rather than write a test for every Screw.Unit test page we write, we made use of JUnit4’s Parameterized test. This way we provide a list of Screw.Unit tests and have the Parameterized test execute a test for each.

Annotate the class:

  1. @RunWith (Parameterized.class)
  2. public class ScrewUnitTest {
  1. Provide a static Collection:
  2. @SuppressWarnings ("unchecked")
  3.     @Parameters
  4.     public static Collection data() {
  5.         Collection fileCollection = new ArrayList();
  6.         Collection fileList = FileUtils.listFiles(new File(HTML_TEST_DIR), extensions, true);
  7.         for (File file : fileList) {
  8.             File[] fileArray = { file };
  9.             fileCollection.add(fileArray);
  10.         }
  11.         return fileCollection;
  12.     }

and a constructor:

  1. public ScrewUnitTest(File file) {
  2.         this.file = file;
  3.     }

Then for every file in the Collection, this.file will be available for reference in the tests. And our JavaScript tests can be easily run by a continuous integration system.

Leave a Reply

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

*

*