Writing Testable Frontend Javascript Part 1 – Anti-patterns and their fixes

This is the first of a two part introduction to writing testable Javascript UI code. The first article presents a basic sample application containing several common anti-patterns and their solutions.

In the second article, the application will be refactored using the described techniques, a simple XHR mock will be presented, and add a test suite will be added to help future developers maintain the code.

Front end development comes with a set of challenges that are rarely discussed in articles about unit testing. Self initialization, encapsulated logic, DOM event handlers, XHR requests, and nested callbacks all make testing difficult.

Fortunately, writing front end code so that it can be tested is straight forward, but it does require a little knowledge and thought.

Common coding practice – Easy to understand, difficult to test

While short, this contrived example uses several common anti-patterns.

Anti-patterns that make this application difficult to test

  • Inline Javascript – Javascript that is embedded in an HTML file is impossible to include in an external unit test harness.
  • Inaccessible code – Even if the Javascript was externalized, no public interface is provided.
  • Missing constructor/prototype object – Individual unit tests are meant to operate in isolation. Testing a singleton is difficult because the results of one test could affect the results of another.
  • Pyramid of doom – Deeply nested callbacks are rampant in Javascript development, but they scream of a mixture of concerns. Logic inside the pyramid is difficult to test in isolation, and over time, have a tendency to turn into unmaintainable spaghetti.
  • Poorly constructed DOM event handlers – Event handler logic is mixed with form submission logic, resulting in an inflexible mixture of concerns.
  • Real XHR Requests – Real XHR requests require an available back end. Rapid parallel development of both the front and back ends is difficult since XHR requests require a working back end.
  • Notification-less asynchronous logic – Without some sort of notification, it is impossible to know when an asynchronous function has completed.

How To Write Testable Javascript UI Code

Each of the outlined problems can be addressed. With a little thought, front end code is easy to test.

Externalize all Javascript

Javascript that is directly embedded into an HTML file is impossible to include into another HTML file. External Javascript is ready for re-use and can be included into more than one HTML file.

Provide a public interface

Code must have a public interface for it to be tested. The Module is the most commonly used pattern to encapsulate logic while providing a public interface. In his excellent book Essential Javascript Design Patterns, Addy Osmani states that “the Module pattern was originally defined as a way to provide both private and public encapsulation for classes in conventional software engineering.”

The original sample application has no public interface; all code is encapsulated inside of a self-invoking function. The only hook that can be used for testing is the submit event. While certainly possible, writing tests using only synthetic events is unnecessarily difficult.

A properly encapsulated Module can be used to limit access to functions, reduce pollution of the global namespace, and provide a public interface from which to test.

As Addy points out, a drawback to the Module pattern is “the inability to create automated unit tests for private members.” A function that is not directly accessible cannot be directly tested. A tension exists in module design between keeping members private and exposing members to the public.

In the Mozilla Persona code base, we frequently expose difficult to test private functions to the public interface, clearly marking the extra functions as part of a test API. While other developers are still able to call these private functions, the author’s intentions are clear.

Code between the // BEGIN TESTING API and //END TESTING API pseudo-markers can be removed for production during the build process.

Use Instantiable Objects

The original application does not use instantiable objects, its code is designed to be run only once. This constraint makes it difficult to reset state and run unit tests independently of each other.

It is easier to test modules that can be instantiated multiple times. In Javascript, two similar approaches exist: a constructor function and Object.create.

Since PublicModule two examples ago is an object and not a function, Object.create is used to create duplicate objects. An optional init function can be added to the prototype to perform initialization that is traditionally done in a constructor.

Flatten the pyramid of doom

Deeply nested callbacks have unfortunately become a staple of front end Javascript programming. The Untestable Authentication Form example is by no means extraordinary with callbacks nested three deep. Deeply nested callbacks are a code smell - they stink of poorly composed functionality and a mixture of concerns.

Breaking the pyramid into constituent components results in flat code that is made up of small, cohesive, and easy to test functions.

Separate DOM event handlers from the action it performs

The Untestable Authentication Form uses a single submit handler to take care of both event processing and form submission. Not only are two concerns taken care of, but this mixture results in an inability to submit the form programmatically without using a synthetic event.

Separating the form processing logic from the event handling logic allows the form to be submitted programmatically without resorting to synthetic events.

Unit tests can call submitForm instead of using a synthetic submit event.

Mock in XHR requests

Almost all modern sites use XHR (AJAX) requests. XHR requests introduce a dependency on the back end; requests from the front end must be answered or else the app will sit idle. Testing with real XHR requests means the front end cannot be tested until the back end is ready, a serious hindrance to parallel development.

Instead of making actual XHR requests, use an XHR mock that responds in a well defined manner. Mock objects "are simulated objects that mimic the behavior of real objects in controlled ways." Mocks are useful in situations where a dependency contains functionality that is unavailable, slow, cannot be controlled, or is too buggy to rely upon. XHR requests are just one example.

Testing both front and back ends together is important, but this is better left to functional tests. Unit tests are meant to test items in isolation.

A module that makes an XHR request should accept an XHR mock that is injected into its constructor or init function. The module then uses the injected XHR object instead of a direct call to $.ajax. A mock XHR object is injected while running unit tests whereas modules running in production use $.ajax.

Sensible defaults can be declared to reduce the amount of initialization code needed for production systems.

Asynchronous programming requires notifications

The Untestable Authentication Form example lacks a notification mechanism to indicate when all processing has completed. This is a problem for unit tests that run after an asynchronous function has finished.

Many notification mechanisms exist in Javascript: callbacks, observables, and events are just a few. The simple callback is by far the most common.

Clean up after yourself

Unit tests should run in isolation of each other; once a test completes, all state should be destroyed, including DOM event handlers. Two tests that both cause objects to bind DOM events handlers to the same DOM element have a good chance of inadvertently interfering with each other. To eliminate this interference, a dead object should remove all of its DOM event handlers. The extra work comes with an additional advantage; memory leaks are greatly reduced in applications that create and destroy many objects.

Summary

That's it. There really isn't much to writing front end Javascript code so that it can be tested. A public interface, instantiable objects, a flat code structure, well composed event handlers, and a way to clean up after yourself.

Code for this article can be found on Github.

In the part 2 of this article, the tips presented here will be used to refactor the initial sample application, write a simple XHR mock, and add a full unit test suite.

Leave a comment ?

19 Comments.

  1. Thanks for the write-up!

    I’ve wanted to learn how to do testing of client side code for a long time.
    Some of the advice are already no problem when coming from the nodejs side and using something like component to build a client side app.
    You get module.exports style modules and instantiable objects for free.
    I especially love component/emitter which beautifully takes care of event handling, at least internally for my classes.

    I often get mixed up in dom-events and form submission. Something I need to get cleaned up. Mixing the inconvenience of the DOM with your own objects/classes is something that’s bothering me a lot. I need to get a clearer cut between the two.

    What I do not agree with is your use of Dependency Injection. Luckily, I never had to do it, but I did however read a lot about it. I consider it a language smell coming from the Java world that simply has no place in the wonderful world of JS where you can just monkey-patch or override anything you want.
    It gets a little harder if I want to test something that depends on, say, the current Date which I want to manipulate in order to have predefined behavior. Maybe instead of using the Date() constructor directly, I might use Date.now() instead and monkey-patch that.

    The problem I have with frontend testing might be a general project structure problem, or my misunderstanding of unit-test vs. functional-test.
    Writing self-contained instantiable classes or widgets and get them tested on their own is not that hard. But when the coupling between components gets waaaay to tight, and the DOM and user interactions come into play, everything is somehow dependent on each other and some global state, things get real nasty real quick.
    Getting that tested is something I have no idea how to do.

    • Hi Arpad! Thanks for comment. I agree with you about using emitters/PubSub/promises, they are great mechanisms to avoid using callbacks directly with the added advantage that any number of subscribers can listen for the event. I debated whether to add a section on their use but thought it might be too advanced for an intro.

      DI is one of those things that is heavily debated, I use both DI and monkey patching. I lean on the side of using DI simply as a way of saying “hey, it IS actually expected that you may want to override this” whereas monkey patching is reaching directly into somebody’s abstraction and doing what I will with it. It feels dirty.

      And finally, yes, a hard boundary between unit and functional tests is difficult to define. Testing centralized state machines is notoriously difficult and they always seem to be pseudo-unit, pseudo-functional.

      The unit test boundary for me is “will this seemingly complex test help me develop this module right now?” if yes, then I write a unit test. A second question I ask myself whether to develop a unit test is “Will a failure in functional/Selenium tests allow me to easily pinpoint that the problem exists here?”

  2. Some really good advice, especially the stuff about mock XHR objects.

    I’m definitely going to try and implement more code like this in my projects.

  3. Good points. I find that the jQuery.Deferred object can really help produce unit-testable code and significantly reduces your “pyramid of doom”

  4. Nice analysis of the challenges of testing JS apps. Better if it’s baked in than bolted on later it seems.

  5. Nice post. Those Anti-Patterns are what I have done for a long time. I always wonder how I can test front-end codes. Thank you for pointing out.

  6. Great post and thank you for taking the time to write this.

    I find that not being able to test private functions of modules is not a big hindrance to writing stable code. Generally in most OOP languages you don’t unit test private methods since they aren’t considered stable and also aren’t part of your object’s public interface.

    • Thanks Richard, I agree with you to a large extent. 90% of the time, the “private” functions that I expose are the “action” portion of an event handler so that I can avoid creating synthetic events. Most other code I manage to exercise in some other manner, like you are saying private functions are frequently not too much of a problem.

  7. Well then maybe the ajax example you gave for DI is not really a good one.

    Anyway, I found sinonjs.org which can mock xhr and timers really nicely. What Sinon can not mock when it comes to Dates is the Timezone/UTC handling, which really IS the biggest PITA when it comes to js Date handling :(

    So I’m looking forward to part 2 :-)

  8. A nice summary of some key techniques, and justifications for them in plain terms. Kudos.

  9. What is interesting about the example presented here is that most of the problems arise from rigid coupling and no separation of concerns. You have rightly identified some “code smells” and have employed some well known refactorings to clean them up.

    E.g., “Separate DOM event handlers from the action it performs” demonstrates the “extract method” refactoring in all its power and simplicity. The subtle dependency that existed between handling the event and doing the thing in response to the event is nicely encapsulated in a method. The next step would be to do a “method object” refactoring and push this responsibility even further away from the page

  10. nice article, decoupling is the key, the “ajax” trick is pretty cool too. +1

  11. Thank you very much for your article. As someone who is new to javascript it is very mind opening. I have a question regarding “… be removed for production during the build process.”

    Does mozilla have a javascript build tool it frequently uses to accomplish this. DOJO toolkit has something to conditionally remove javascript. Do you use any premade build tools or do you have your own??

    Thank you very much

    • I am glad you like the article! We use UglifyJS to remove comments and minify the code, but we have been leaving the code between the // BEGIN TESTING API and // END TESTING API in place. If you need to remove similar portions of code, any tool that supports regular expression matching and string replacement will work; sed, perl and even Node are three tools I commonly use.

  12. Nice article! Well done!

    Now please give us part 2!

  13. Great article!

    The before and after examples are clear and easy to understand. Excellent sharing!

  14. Thanks for a very informative article. I’m somewhat new to unit testing and your advice on better organising event handling is invaluable.
    During your talk through of the module pattern this phrase struck me ‘expose difficult to test private functions to the public interface clearly marking the extra functions as part of a test API’. I was wondering if you could sandbox those publicly accessible hooks a bit more to make the code overall more robust yet whose private APIs still testable. I came up with a ‘__TestPrivateMethods’ proxy function that would be defined in just one place in an app (anywhere would do) and would allow the testing of private methods… the advantage being that once the code was ready for production then the definition of this ‘__TPM’ could be removed (i.e. you just have to do it one place) and so the private methods go back to being 100% private.

  15. Oops sorry forgot to give the URI for the jBIN with the code.

    http://jsbin.com/UhEGokEH/7/watch?html,js,output

    PS I can’t say I like the assert vocabulary qUnit gives you…but that’s another story

Leave a Comment

Post comment

What is Persona?