You probably know that testing is good, but the first hurdle to overcome when trying to write unit tests for client-side code is the lack of any actual units; JavaScript code is written for each page of a website or each module of an application and is closely intermixed with back-end logic and related HTML. In the worst case, the code is completely mixed with HTML, as inline events handlers.

This is likely the case when no JavaScript library for some DOM abstraction is being used; writing inline event handlers is much easier than using the DOM APIs to bind those events. More and more developers are picking up a library such as jQuery to handle the DOM abstraction, allowing them to move those inline events to distinct scripts, either on the same page or even in a separate JavaScript file. However, putting the code into separate files doesn’t mean that it is ready to be tested as a unit.

What is a unit anyway? In the best case, it is a pure function that you can deal with in some way — a function that always gives you the same result for a given input. This makes unit testing pretty easy, but most of the time you need to deal with side effects, which here means DOM manipulations. It’s still useful to figure out which units we can structure our code into and to build unit tests accordingly.

Building Unit Tests

With that in mind, we can obviously say that starting with unit testing is much easier when starting something from scratch. But that’s not what this article is about. This article is to help you with the harder problem: extracting existing code and testing the important parts, potentially uncovering and fixing bugs in the code.

The process of extracting code and putting it into a different form, without modifying its current behavior, is called refactoring. Refactoring is an excellent method of improving the code design of a program; and because any change could actually modify the behaviour of the program, it is safest to do when unit tests are in place.

This chicken-and-egg problem means that to add tests to existing code, you have to take the risk of breaking things. So, until you have solid coverage with unit tests, you need to continue manually testing to minimize that risk.

That should be enough theory for now. Let’s look at a practical example, testing some JavaScript code that is currently mixed in with and connected to a page. The code looks for links with title attributes, using those titles to display when something was posted, as a relative time value, like “5 days ago”:

If you ran that example, you’d see a problem: none of the dates get replaced. The code works, though. It loops through all anchors on the page and checks for a title property on each. If there is one, it passes it to the prettyDate function. If prettyDate returns a result, it updates the innerHTML of the link with the result.

Make Things Testable

The problem is that for any date older than 31 days, prettyDate just returns undefined (implicitly, with a single return statement), leaving the text of the anchor as is. So, to see what’s supposed to happen, we can hardcode a “current” date:

Now, the links should say “2 hours ago,” “Yesterday” and so on. That’s something, but still not an actual testable unit. So, without changing the code further, all we can do is try to test the resulting DOM changes. Even if that did work, any small change to the markup would likely break the test, resulting in a really bad cost-benefit ratio for a test like that.

Refactoring, Stage 0

Instead, let’s refactor the code just enough to have something that we can unit test.

We need to make two changes for this to happen: pass the current date to the prettyDate function as an argument, instead of having it just use new Date, and extract the function to a separate file so that we can include the code on a separate page for unit tests.

Run this example.3 (Make sure to enable a console such as Firebug or Chrome’s Web Inspector.)

This will create an ad-hoc testing framework, using only the console for output. It has no dependencies to the DOM at all, so you could just as well run it in a non-browser JavaScript environment, such as Node.js or Rhino, by extracting the code in the script tag to its own file.

If a test fails, it will output the expected and actual result for that test. In the end, it will output a test summary with the total, failed and passed number of tests.

If all tests have passed, like they should here, you would see the following in the console:

Of 6 tests, 0 failed, 6 passed.

To see what a failed assertion looks like, we can change something to break it:

Expected 2 day ago, but was 2 days ago.

Of 6 tests, 1 failed, 5 passed.

While this ad-hoc approach is interesting as a proof of concept (you really can write a test runner in just a few lines of code), it’s much more practical to use an existing unit testing framework that provides better output and more infrastructure for writing and organizing tests.

The QUnit JavaScript Test Suite

The choice of framework is mostly a matter of taste. For the rest of this article, we’ll use QUnit4 (pronounced “q-unit”), because its style of describing tests is close to that of our ad-hoc test framework.

Three sections are worth a closer look here. Along with the usual HTML boilerplate, we have three included files: two files for QUnit (qunit.css and qunit.js) and the previous prettydate.js.

Then, there’s another script block with the actual tests. The test method is called once, passing a string as the first argument (naming the test) and passing a function as the second argument (which will run the actual code for this test). This code then defines the now variable, which gets reused below, then calls the equal method a few times with varying arguments. The equal method is one of several assertions that QUnit provides. The first argument is the result of a call to prettyDate, with the now variable as the first argument and a date string as the second. The second argument to equal is the expected result. If the two arguments to equal are the same value, then the assertion will pass; otherwise, it will fail.

Finally, in the body element is some QUnit-specific markup. These elements are optional. If present, QUnit will use them to output the test results.

Because the test contains a failing assertion, QUnit doesn’t collapse the results for that test, and we can see immediately what went wrong. Along with the output of the expected and actual values, we get a diff between the two, which can be useful for comparing larger strings. Here, it’s pretty obvious what went wrong.

Refactoring, Stage 1

The assertions are currently somewhat incomplete because we aren’t yet testing the n weeks ago variant. Before adding it, we should consider refactoring the test code. Currently, we are calling prettyDate for each assertion and passing the now argument. We could easily refactor this into a custom assertion method:

Here we’ve extracted the call to prettyDate into the date function, inlining the now variable into the function. We end up with just the relevant data for each assertion, making it easier to read, while the underlying abstraction remains pretty obvious.

Testing The DOM manipulation

Now that the prettyDate function is tested well enough, let’s shift our focus back to the initial example. Along with the prettyDate function, it also selected some DOM elements and updated them, within the window load event handler. Applying the same principles as before, we should be able to refactor that code and test it. In addition, we’ll introduce a module for these two functions, to avoid cluttering the global namespace and to be able to give these individual functions more meaningful names.

The new prettyDate.update function is an extract of the initial example, but with the now argument to pass through to prettyDate.format. The QUnit-based test for that function starts by selecting all a elements within the #qunit-fixture element. In the updated markup in the body element, the <div id="qunit-fixture">…</div> is new. It contains an extract of the markup from our initial example, enough to write useful tests against. By putting it in the #qunit-fixture element, we don’t have to worry about DOM changes from one test affecting other tests, because QUnit will automatically reset the markup after each test.

Let’s look at the first test for prettyDate.update. After selecting those anchors, two assertions verify that these have their initial text values. Afterwards, prettyDate.update is called, passing along a fixed date (the same as in previous tests). Afterwards, two more assertions are run, now verifying that the innerHTML property of these elements have the correctly formatted date, “2 hours ago” and “Yesterday.”

Refactoring, Stage 2

The next test, prettyDate.update, one day later, does nearly the same thing, except that it passes a different date to prettyDate.update and, therefore, expects different results for the two links. Let’s see if we can refactor these tests to remove the duplication.

Here we have a new function called domtest, which encapsulates the logic of the two previous calls to test, introducing arguments for the test name, the date string and the two expected strings. It then gets called twice.

Back To The Start

With that in place, let’s go back to our initial example and see what that looks like now, after the refactoring.

For a non-static example, we’d remove the argument to prettyDate.update. All in all, the refactoring is a huge improvement over the first example. And thanks to the prettyDate module that we introduced, we can add even more functionality without clobbering the global namespace.

Conclusion

Testing JavaScript code is not just a matter of using some test runner and writing a few tests; it usually requires some heavy structural changes when applied to code that has been tested only manually before. We’ve walked through an example of how to change the code structure of an existing module to run some tests using an ad-hoc testing framework, then replacing that with a more full-featured framework to get useful visual results.

QUnit itself has a lot more to offer, with specific support for testing asynchronous code such as timeouts, AJAX and events. Its visual test runner helps to debug code by making it easy to rerun specific tests and by providing stack traces for failed assertions and caught exceptions. For further reading, check out the QUnit Cookbook12.

Jörn is a freelance web developer, consultant and trainer, residing in Cologne, Germany.
Jörn evolved jQuery’s testsuite into QUnit, a JavaScript unit testing framework, and maintains it. He created and maintains a number of popular plugins.
As a jQuery UI development lead, he focuses on the development of new plugins, widgets and utilities.

Mark Simchock

Pat

I generally write my JS modules using the Revealing Module Pattern, i.e. only exposing some methods while keeping others internal to object. I’m looking to integrate writing tests (in particular jasmine.js) into my development workflow but still can’t figure out how to apply tests to my “private” code.

Have you had any experience with this? In all the examples I’ve seen, the functions being tested are all public.

Pedja

Jörn Zaefferer

@Pat: Why make methods private in the first place? You can already see the obvious drawback to that.

Less obvious are the performance implications of that pattern. Usually a prototypal based inheritance has much less overhead and encourages keeping individual methods public, which makes testing quite easy. If you actually worry about defining a public API, consider making it clear which methods are actually part of the public API, and which aren’t, but don’t try to actually hide anything. In my experience: Not worth it.

Clayton

@Pat To answer your question, you can write unit tests for objects with private methods by testing the behavior of your object, not the specific implementation. If you’re using Jasmine, you should find this pretty straightforward since so many of the examples are geared towards describing behavior.

Polly

Bleyder

I think there is a typo in the code of section “Testing The DOM manipulation”. In the “prettyDate.update, one day later” test, the date argument passed to prettyDate.update function should be “2008-01-29T22:25:00Z”, not “2008-01-28T22:25:00Z”.

Ivan Ketler

First of all, thanks for the paper and the info. Currently I’m involved in GUI unit testing, and with the heavyweight stuff like Java Swing I’m quite fine, but I needed something for the HTML5/Ajax/JS world too, and this QUnit seems to be a good start for me.

The point is well taken, and makes it easy even for a newbie to start unit testing JS code. Great work.

There’s just one note. IMHO, the “Refactoring, Stage 1″ rather hides the meaning than makes it easier to understand. That “date” function gives no real value, just hinders the “now” into somewhere else. I’m not sure that I’d even introduce the “now” local variable, and I’m pretty sure that I wouldn’t ever refactor it. The first version is much more readable for me, and I suggest others to consider why we should make the tests any shorter. The easier to understand a test is the better, especially if one wants to write the tests first, and design the code only afterwards (Test Driven Development, or even better, Behaviour Driven Development). As far as I see, the JavaScript has the general assumption of being “unreadable”, and thus hardly maintainable, mostly because many of the JS experts “abbreviate” their code (that is, obfuscate it).

Jimmy

As Bleyder mentioned over a year ago, there’s a typo in the test for ‘prettyDate.update, one day late’. Regrettably, the typo is that the ‘now’ date is identical to the previous test, which is only obviously wrong because of the refactoring in the next section. On first reading, it’s not obvious what is being demonstrated, due to this typo.

dsfafdas

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

SmashingConf isn't the eighth wonder of the world, but we are pretty close. Join us at SmashingConf Oxford on March 16–19 or meet us at the shores of Santa Monica for SmashingConf LA on April 27–30. You won't be disappointed.