Tools

Unit Testing in JavaScript

by on December 16, 2013 8:46 am

JavaScript has come a long way. There was a time it was easily dismissed – maybe suitable for noncritical validations, but not much else.

Over the years, the worst of the obstacles to writing large-scale JavaScript programs have fallen away, or at least been mitigated. JavaScript dialects are more consistent among browsers than they once were, and tools like jQuery can help smooth over the differences that still exist. Debuggers are at least not as terrible as they once were (though a lot still depends on what browser you can use to debug).

And in any case, with the advent of HTML5 and mobile platforms, JavaScript is becoming much harder to ignore. Whole application front-ends are being written in JavaScript, and while the tool support lags well behind what we expect in Java or C#, it’s evolving.

One area that remains particularly challenging, and that will become incredibly important as JavaScript projects grow in scope, is unit testing.

Choosing a Framework

If you write Java, you probably don’t spend much time deciding on a unit test framework. I can’t remember the last Java project I worked on that didn’t use jUnit. (Mock object frameworks may be another matter, but I’ll come back to that.) The JavaScript landscape isn’t as settled, and which framework you choose can profoundly affect your testing capabilities.

qUnit is a good place to start. It’s reasonably easy to learn; I found a couple of its behaviors less than intuitive, but once I understood the problems those behaviors were meant to address, everything “clicked” for me. It allows for interaction with the DOM; you can identify a DIV on your test page to be reset to its original state between tests, promoting test atomicity as you can “tee up” a single starting state. And it supports “asynchronous tests,” which I’ll explain shortly; for many apps, this will be a vital feature.

js-test-driver focuses on parallel testing in multiple browsers. It has its own language of test cases and assertions, distinct from qUnit’s. (There are projects that allow the two to be used together, though as of now I’ve not found one that fully supports all capabilities of both tools.) Unlike qUnit, it doesn’t use part of the DOM to report results and it resets the entire DOM between test runs. This imposes one less constraint on the code to be tested.

And these are just two of many options, as a quick search of Wikipedia confirms.

Test Execution Environment

This is another question that, at least in my experience, seems much more complicated with JavaScript than with Java: “In what environment should I run my tests?”

You should always want your test environment to match your production deployment environment as closely as possible. But for many JavaScript apps, a critical piece of that environment – the browser with its attendant JavaScript engine – is chosen by the user. Even if cross-browser issues aren’t as bad as they once were, it would still be folly to test only in one browser and assume the rest will fall in line.

At the same time, testing in even one browser poses challenges when you start thinking about automated testing and CI. (This is the big challenge that js-test-driver is meant to address. If you want a pure qUnit solution, you’ll probably find yourself either writing your own driver scripts or doing without full automation.)

There are easier-to-script JavaScript runtime environments, like NodeJS, which may be useful for some level of automated testing; but these may have their own limitations (NodeJS doesn’t have native DOM support, though add-ons exist) and in any case may not match your target browser’s behavior.

A multi-layered approach might suit your needs. You could script some tests to run in NodeJS for CI, and then periodically launch a more comprehensive (but more manual) test suite in each target browser. Some bugs might not be caught as early as with a full CI suite, of course.

But then, that’s the main take-away: as the tools stand today, unit testing in JavaScript is a world of choices, trade-offs, and compromises.

Code Structure

When jUnit was young, it wasn’t uncommon to find Java code that didn’t submit easily to unit testing. Over the years, this became seen as a test of the quality of a design. JavaScript has, at best, just begun to develop this discipline, and some of the language features that push Java developers in the right direction (like encapsulation) don’t enjoy the same level of native support in JavaScript.

There are frameworks that can help you steer toward writing in testable units. I recently worked with ExtJS, for example, which puts a class system on top of the JavaScript object model. Tools like this can help if you let them, but as they aren’t a native part of the language, you can always choose to go against their grain. Writing testable JavaScript is, and for the foreseeable future will remain, an art you must choose to practice.

I don’t think there’s any one “right” way to do it, but in general moving away from a lot of anonymous inline script and toward functions (either with names or assigned to accessible variables), preferably in separate code files, goes a long way.

Even if you write every line of code with an eye on testability, though, one challenging quirk will emerge in many applications: ubiquity of asynchronous behavior. Every major JavaScript engine is single-threaded, yet common tasks like requesting data from the server (AJAX), responding to the UI, or explicitly delaying an activity (using setTimeout or setInterval) all act asynchronously. Midway through someFunction() you set the ball in motion that will cause anotherFunction() to execute, but anotherFunction() ends up in a queue where it will sit until after someFunction() has returned. There may even be other functions in the queue ahead of anotherFunction().

So what’s a test runner to do? You expect to bracket the code you’re testing.

  1. Framework sets up to run the test
  2. Test code sets preconditions and makes a call to the code to be tested
  3. Code to be tested does something interesting
  4. Test code checks assertions
  5. Framework reports results and moves on to the next test

But what if the code to be tested does something asynchronously? Here’s one way that might play out:

  1. Framework sets up to run the test
  2. Test code sets preconditions and makes a call to the code to be tested
  3. Code to be tested starts to do something interesting, and then it queues up a function to finish the job
  4. Framework reports (inaccurate) results and moves on to the next test
  5. Other queued functions may run
  6. The queued part of the code to be tested gets its turn to run, and finishes the interesting thing that was started in Step 3
  7. Test code checks assertions

At best assertions get reported against the wrong test. More likely you get a bunch of tests failing for no good reason.

qUnit addresses this with the asynchronousTest() function. This works just like the regular test() function, except the framework doesn’t assume the test is over when the test code returns. You have to tell it when the test is over by calling start(). (In practice, test code that’s dealing with asynchronous behavior will consist, in part, of event handlers or other callback functions. This is where you’ll test final assertions and, if there are no more asynchronous events to be triggered, call start().)

If you think this all seems a little backwards, you’re not alone. You call start() when you want to end your test, because it tells the framework to (re)start processing. This whole mechanism causes tests to run in lockstep, which seems synchronous; but you call asynchronousTest() because the name refers to the asynchronous nature of the code you’re testing.

Mock Objects

I suppose this has all seemed a little pessimistic, so let’s end on a ray of sunshine. The dynamic nature of JavaScript gives you all kinds of flexibility to write mock objects, even without using a mock object framework.

In Java, your mock object has to share an interface with, and/or be a subclass of, the class it replaces. Depending on the sophistication of your mocking framework, this can impose various restrictions and/or make you jump through hoops to get the job done. In JavaScript, you can just build an object that implements exactly the mock behavior you want, and it needn’t bear any special relationship to its production counterpart. Of course it still helps if your code is written with inversion of control in mind, but if it’s not then at least you may benefit from the relative ease of intercepting a function call in JavaScript.

Wrapping it Up…

It would be easy to overlook unit testing as you start to embark on large-scale projects in JavaScript, but the need for unit tests is just as real as in any other language. As of today, unit testing tools and procedures aren’t as clear-cut for JavaScript as for Java and other languages that have longer tenures in the full-scale application development world. There are more decisions to make, more trade-offs to consider, and more hurdles to clear.

Hopefully this discussion has you thinking about some of the decisions you’ll have to make.

– Mark Adelsberger, asktheteam@keyholesoftware.com

  • Share:

One Response to “Unit Testing in JavaScript”

  1. David says:

    Mark,

    Thanks for article, I also believe unit test is something that often happens as an after thought. On a side note, phantom.js is a headless browser javasript environment that has a DOM and can integrae well in CI environment.s Regarding mocking, i’ve had good luck with the jQuery MockJax plugin. Thanks, David

Leave a Reply

Things Twitter is Talking About
  • DYK? When we share/RT/blog/etc, it doesn't mean that Keyhole endorses it - we just like variety of opinions! Info: http://t.co/MXhB9lE9tV
    April 19, 2014 at 3:01 PM
  • A huge welcome to Justin Graber who joined the Keyhole team this week!
    April 18, 2014 at 3:25 PM
  • Pssst... @kc_dc early bird pricing ends on Sunday. Shoot us a note if you want to save 10% off of your ticket with our sponsor promo code!
    April 18, 2014 at 2:49 PM
  • Join our team! Looking for a developer w/ advanced #JavaScript & #NodeJS experience (& love of tech variety). Info: http://t.co/cC9CU1RCF9
    April 18, 2014 at 11:21 AM
  • .@befamous has huge potential to make HTML5/JS/CSS web pages feel as native apps. Here's our inital tech takeaways - http://t.co/S77TSKHDKd
    April 18, 2014 at 9:50 AM
  • Why to use AngularUI Router instead of ngRoute - http://t.co/tBnj5ZCkOw
    April 17, 2014 at 7:55 PM
  • RT @joemccann: Total Number of GitHub Repositories by Programming Language http://t.co/30cekDsE4s
    April 17, 2014 at 4:25 PM
  • JSF + AngularJS = AngularFaces? http://t.co/mXvOTwVbb6 // Interesting insight. Thoughts?
    April 17, 2014 at 3:45 PM
  • RT @MikeGelphman: Great news, guys: @TobiasRush founder of @eyeverify is our latest @MobileMidwest speaker addition http://t.co/8fE8oNfPnX
    April 17, 2014 at 1:35 PM
  • .@befamous was released publicly 4/10 & we've been tinkering with it since. What we've learned so far via a POC app - http://t.co/S77TSKHDKd
    April 17, 2014 at 8:33 AM
  • Famo.us' main idea is for HTML5/JS/CSS web pages to feel like native mobile apps. So, @zachagardner tried it out - http://t.co/S77TSKHDKd
    April 15, 2014 at 6:40 PM
  • @JKFeldkamp Thanks for your RT! Such a neat technology. We're so excited @zachagardner is getting involved. Have a great day!
    April 15, 2014 at 4:00 PM
  • .@zachagardner has been tinkering with Famo.us (@befamous) released 4/10. What he's learned so far with a POC app - http://t.co/1jMqBfZURn
    April 15, 2014 at 2:29 PM
  • Tutorial: create #RabbitMQ Template to send msg to an exchange & listen for msgs with a routing key pattern - http://t.co/qDbq6TrxtW
    April 11, 2014 at 10:02 AM
  • There's a great #KC conference coming up on April 23rd - @KCITP's Mobile Midwest http://t.co/CuQGby6kvD Shift into a “Mobile First” mindset!
    April 10, 2014 at 3:59 PM
  • Interesting - 6 #programming paradigms that change how u think about coding: http://t.co/QpRdx76Sn2 & its discussion: http://t.co/DVBRstecba
    April 10, 2014 at 10:11 AM
  • DYK? When we share/RT/blog/etc, it doesn't mean that Keyhole endorses it - we just like variety of opinions! Info: http://t.co/MXhB9lE9tV
    April 9, 2014 at 2:13 PM
  • Developers, need a chuckle? 12 Problems Only Programmers Understand - http://t.co/8PxJSYg0FA #funny
    April 9, 2014 at 2:00 PM
  • Immediately looking to add to our team a Sr. C# developer with knowledge of #NodeJS, #Marionette & #MongoDB. Details: http://t.co/Yyq0b6iza3
    April 9, 2014 at 1:27 PM
  • A huge welcome to Vince Pendergrass who joins the Keyhole team this week!
    April 8, 2014 at 2:37 PM