Writing Testable JavaScript

We’ve all been there: that bit of JavaScript functionality that started out as just a handful of lines grows to a dozen, then two dozen, then more. Along the way, a function picks up a few more arguments; a conditional picks up a few more conditions. And then one day, the bug report comes in: something’s broken, and it’s up to us to untangle the mess.

Article Continues Below

As we ask our client-side code to take on more and more responsibilities—indeed, whole applications are living largely in the browser these days—two things are becoming clear. One, we can’t just point and click our way through testing that things are working as we expect; automated tests are key to having confidence in our code. Two, we’re probably going to have to change how we write our code in order to make it possible to write tests.

Really, we need to change how we code? Yes—because even if we know that automated tests are a good thing, most of us are probably only able to write integration tests right now. Integration tests are valuable because they focus on how the pieces of an application work together, but what they don’t do is tell us whether individual units of functionality are behaving as expected.

That’s where unit testing comes in. And we’ll have a very hard time writing unit tests until we start writing testable JavaScript.

Unit vs. integration: what’s the difference?#section2

Writing integration tests is usually fairly straightforward: we simply write code that describes how a user interacts with our app, and what the user should expect to see as she does. Selenium is a popular tool for automating browsers. Capybara for Ruby makes it easy to talk to Selenium, and there are plenty of tools for other languages, too.

Here’s an integration test for a portion of a search app:

def test_search
  fill_in('q', :with => 'cat')
  find('.btn').click
  assert( find('#results li').has_content?('cat'), 'Search results are shown' )
  assert( page.has_no_selector?('#results li.no-results'), 'No results is not shown' )
end

Whereas an integration test is interested in a user’s interaction with an app, a unit test is narrowly focused on a small piece of code:

When I call a function with a certain input, do I receive the expected output?

Apps that are written in a traditional procedural style can be very difficult to unit test—and difficult to maintain, debug, and extend, too. But if we write our code with our future unit testing needs in mind, we will not only find that writing the tests becomes more straightforward than we might have expected, but also that we’ll simply write better code, too.

To see what I’m talking about, let’s take a look at a simple search app:

Srchr

When a user enters a search term, the app sends an XHR to the server for the corresponding data. When the server responds with the data, formatted as JSON, the app takes that data and displays it on the page, using client-side templating. A user can click on a search result to indicate that he “likes” it; when this happens, the name of the person he liked is added to the “Liked” list on the right-hand side.

A “traditional” JavaScript implementation of this app might look like this:

var tmplCache = {};

function loadTemplate (name) {
  if (!tmplCache[name]) {
    tmplCache[name] = $.get('/templates/' + name);
  }
  return tmplCache[name];
}

$(function () {

  var resultsList = $('#results');
  var liked = $('#liked');
  var pending = false;

  $('#searchForm').on('submit', function (e) {
    e.preventDefault();

    if (pending) { return; }

    var form = $(this);
    var query = $.trim( form.find('input[name="q"]').val() );

    if (!query) { return; }

    pending = true;

    $.ajax('/data/search.json', {
      data : { q: query },
      dataType : 'json',
      success : function (data) {
        loadTemplate('people-detailed.tmpl').then(function (t) {
          var tmpl = _.template(t);
          resultsList.html( tmpl({ people : data.results }) );
          pending = false;
        });
      }
    });

    $('<li>', {
      'class' : 'pending',
      html : 'Searching &hellip;'
    }).appendTo( resultsList.empty() );
  });

  resultsList.on('click', '.like', function (e) {
    e.preventDefault();
    var name = $(this).closest('li').find('h2').text();
    liked.find('.no-results').remove();
    $('<li>', { text: name }).appendTo(liked);
  });

});

My friend Adam Sontag calls this Choose Your Own Adventure code—on any given line, we might be dealing with presentation, or data, or user interaction, or application state. Who knows! It’s easy enough to write integration tests for this kind of code, but it’s hard to test individual units of functionality.

What makes it hard? Four things:

  • A general lack of structure; almost everything happens in a $(document).ready() callback, and then in anonymous functions that can’t be tested because they aren’t exposed.
  • Complex functions; if a function is more than 10 lines, like the submit handler, it’s highly likely that it’s doing too much.
  • Hidden or shared state; for example, since pending is in a closure, there’s no way to test whether the pending state is set correctly.
  • Tight coupling; for example, a $.ajax success handler shouldn’t need direct access to the DOM.

Organizing our code#section3

The first step toward solving this is to take a less tangled approach to our code, breaking it up into a few different areas of responsibility:

  • Presentation and interaction
  • Data management and persistence
  • Overall application state
  • Setup and glue code to make the pieces work together

In the “traditional” implementation shown above, these four categories are intermingled—on one line we’re dealing with presentation, and two lines later we might be communicating with the server.

Code Lines

While we can absolutely write integration tests for this code—and we should!—writing unit tests for it is pretty difficult. In our functional tests, we can make assertions such as “when a user searches for something, she should see the appropriate results,” but we can’t get much more specific. If something goes wrong, we’ll have to track down exactly where it went wrong, and our functional tests won’t help much with that.

If we rethink how we write our code, though, we can write unit tests that will give us better insight into where things went wrong, and also help us end up with code that’s easier to reuse, maintain, and extend.

Our new code will follow a few guiding principles:

  • Represent each distinct piece of behavior as a separate object that falls into one of the four areas of responsibility and doesn’t need to know about other objects. This will help us avoid creating tangled code.
  • Support configurability, rather than hard-coding things. This will prevent us from replicating our entire HTML environment in order to write our tests.
  • Keep our objects’ methods simple and brief. This will help us keep our tests simple and our code easy to read.
  • Use constructor functions to create instances of objects. This will make it possible to create “clean” copies of each piece of code for the sake of testing.

To start with, we need to figure out how we’ll break our application into different pieces. We’ll have three pieces dedicated to presentation and interaction: the Search Form, the Search Results, and the Likes Box.

Application Views

We’ll also have a piece dedicated to fetching data from the server and a piece dedicated to gluing everything together.

Let’s start by looking at one of the simplest pieces of our application: the Likes Box. In the original version of the app, this code was responsible for updating the Likes Box:

var liked = $('#liked');

var resultsList = $('#results');


// ...


resultsList.on('click', '.like', function (e) {
  e.preventDefault();

  var name = $(this).closest('li').find('h2').text();

  liked.find( '.no-results' ).remove();

  $('<li>', { text: name }).appendTo(liked);

});

The Search Results piece is completely intertwined with the Likes Box piece and needs to know a lot about its markup. A much better and more testable approach would be to create a Likes Box object that’s responsible for manipulating the DOM related to the Likes Box:

var Likes = function (el) {
  this.el = $(el);
  return this;
};

Likes.prototype.add = function (name) {
  this.el.find('.no-results').remove();
  $('<li>', { text: name }).appendTo(this.el);
};

This code provides a constructor function that creates a new instance of a Likes Box. The instance that’s created has an .add() method, which we can use to add new results. We can write a couple of tests to prove that it works:

var ul;

setup(function(){
  ul = $('<ul><li class="no-results"></li></ul>');
});

test('constructor', function () {
  var l = new Likes(ul);
  assert(l);
});

test('adding a name', function () {
  var l = new Likes(ul);
  l.add('Brendan Eich');

  assert.equal(ul.find('li').length, 1);
  assert.equal(ul.find('li').first().html(), 'Brendan Eich');
  assert.equal(ul.find('li.no-results').length, 0);
});

Not so hard, is it? Here we’re using Mocha as the test framework, and Chai as the assertion library. Mocha provides the test and setup functions; Chai provides assert. There are plenty of other test frameworks and assertion libraries to choose from, but for the sake of an introduction, I find these two work well. You should find the one that works best for you and your project—aside from Mocha, QUnit is popular, and Intern is a new framework that shows a lot of promise.

Our test code starts out by creating an element that we’ll use as the container for our Likes Box. Then, it runs two tests: one is a sanity check to make sure we can make a Likes Box; the other is a test to ensure that our .add() method has the desired effect. With these tests in place, we can safely refactor the code for our Likes Box, and be confident that we’ll know if we break anything.

Our new application code can now look like this:

var liked = new Likes('#liked');
var resultsList = $('#results');



// ...



resultsList.on('click', '.like', function (e) {
  e.preventDefault();

  var name = $(this).closest('li').find('h2').text();

  liked.add(name);
});

The Search Results piece is more complex than the Likes Box, but let’s take a stab at refactoring that, too. Just as we created an .add() method on the Likes Box, we also want to create methods for interacting with the Search Results. We’ll want a way to add new results, as well as a way to “broadcast” to the rest of the app when things happen within the Search Results—for example, when someone likes a result.

var SearchResults = function (el) {
  this.el = $(el);
  this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) );
};

SearchResults.prototype.setResults = function (results) {
  var templateRequest = $.get('people-detailed.tmpl');
  templateRequest.then( _.bind(this._populate, this, results) );
};

SearchResults.prototype._handleClick = function (evt) {
  var name = $(evt.target).closest('li.result').attr('data-name');
  $(document).trigger('like', [ name ]);
};

SearchResults.prototype._populate = function (results, tmpl) {
  var html = _.template(tmpl, { people: results });
  this.el.html(html);
};

Now, our old app code for managing the interaction between Search Results and the Likes Box could look like this:

var liked = new Likes('#liked');
var resultsList = new SearchResults('#results');


// ...


$(document).on('like', function (evt, name) {
  liked.add(name);
})

It’s much simpler and less entangled, because we’re using the document as a global message bus, and passing messages through it so individual components don’t need to know about each other. (Note that in a real app, we’d use something like Backbone or the RSVP library to manage events. We’re just triggering on document to keep things simple here.) We’re also hiding all the dirty work—such as finding the name of the person who was liked—inside the Search Results object, rather than having it muddy up our application code. The best part: we can now write tests to prove that our Search Results object works as we expect:

var ul;
var data = [ /* fake data here */ ];

setup(function () {
  ul = $('<ul><li class="no-results"></li></ul>');
});

test('constructor', function () {
  var sr = new SearchResults(ul);
  assert(sr);
});

test('display received results', function () {
  var sr = new SearchResults(ul);
  sr.setResults(data);

  assert.equal(ul.find('.no-results').length, 0);
  assert.equal(ul.find('li.result').length, data.length);
  assert.equal(
    ul.find('li.result').first().attr('data-name'),
    data[0].name
  );
});

test('announce likes', function() {
  var sr = new SearchResults(ul);
  var flag;
  var spy = function () {
    flag = [].slice.call(arguments);
  };

  sr.setResults(data);
  $(document).on('like', spy);

  ul.find('li').first().find('.like.btn').click();

  assert(flag, 'event handler called');
  assert.equal(flag[1], data[0].name, 'event handler receives data' );
});

The interaction with the server is another interesting piece to consider. The original code included a direct $.ajax() request, and the callback interacted directly with the DOM:

$.ajax('/data/search.json', {
  data : { q: query },
  dataType : 'json',
  success : function( data ) {
    loadTemplate('people-detailed.tmpl').then(function(t) {
      var tmpl = _.template( t );
      resultsList.html( tmpl({ people : data.results }) );
      pending = false;
    });
  }
});

Again, this is difficult to write a unit test for, because so many different things are happening in just a few lines of code. We can restructure the data portion of our application as an object of its own:

var SearchData = function () { };

SearchData.prototype.fetch = function (query) {
  var dfd;

  if (!query) {
    dfd = $.Deferred();
    dfd.resolve([]);
    return dfd.promise();
  }

  return $.ajax( '/data/search.json', {
    data : { q: query },
    dataType : 'json'
  }).pipe(function( resp ) {
    return resp.results;
  });
};

Now, we can change our code for getting the results onto the page:

var resultsList = new SearchResults('#results');

var searchData = new SearchData();

// ...

searchData.fetch(query).then(resultsList.setResults);

Again, we’ve dramatically simplified our application code, and isolated the complexity within the Search Data object, rather than having it live in our main application code. We’ve also made our search interface testable, though there are a couple caveats to bear in mind when testing code that interacts with the server.

The first is that we don’t want to actually interact with the server—to do so would be to reenter the world of integration tests, and because we’re responsible developers, we already have tests that ensure the server does the right thing, right? Instead, we want to “mock” the interaction with the server, which we can do using the Sinon library. The second caveat is that we should also test non-ideal paths, such as an empty query.

test('constructor', function () {
  var sd = new SearchData();
  assert(sd);
});

suite('fetch', function () {
  var xhr, requests;

  setup(function () {
    requests = [];
    xhr = sinon.useFakeXMLHttpRequest();
    xhr.onCreate = function (req) {
      requests.push(req);
    };
  });

  teardown(function () {
    xhr.restore();
  });

  test('fetches from correct URL', function () {
    var sd = new SearchData();
    sd.fetch('cat');

    assert.equal(requests[0].url, '/data/search.json?q=cat');
  });

  test('returns a promise', function () {
    var sd = new SearchData();
    var req = sd.fetch('cat');

    assert.isFunction(req.then);
  });

  test('no request if no query', function () {
    var sd = new SearchData();
    var req = sd.fetch();
    assert.equal(requests.length, 0);
  });

  test('return a promise even if no query', function () {
    var sd = new SearchData();
    var req = sd.fetch();

    assert.isFunction( req.then );
  });

  test('no query promise resolves with empty array', function () {
    var sd = new SearchData();
    var req = sd.fetch();
    var spy = sinon.spy();

    req.then(spy);

    assert.deepEqual(spy.args[0][0], []);
  });

  test('returns contents of results property of the response', function () {
    var sd = new SearchData();
    var req = sd.fetch('cat');
    var spy = sinon.spy();

    requests[0].respond(
      200, { 'Content-type': 'text/json' },
      JSON.stringify({ results: [ 1, 2, 3 ] })
    );

    req.then(spy);

    assert.deepEqual(spy.args[0][0], [ 1, 2, 3 ]);
  });
});

For the sake of brevity, I’ve left out the refactoring of the Search Form, and also simplified some of the other refactorings and tests, but you can see a finished version of the app here if you’re interested.

When we’re done rewriting our application using testable JavaScript patterns, we end up with something much cleaner than what we started with:

$(function() {
  var pending = false;

  var searchForm = new SearchForm('#searchForm');
  var searchResults = new SearchResults('#results');
  var likes = new Likes('#liked');
  var searchData = new SearchData();

  $(document).on('search', function (event, query) {
    if (pending) { return; }

    pending = true;

    searchData.fetch(query).then(function (results) {
      searchResults.setResults(results);
      pending = false;
    });

    searchResults.pending();
  });

  $(document).on('like', function (evt, name) {
    likes.add(name);
  });
});

Even more important than our much cleaner application code, though, is the fact that we end up with a codebase that is thoroughly tested. That means we can safely refactor it and add to it without the fear of breaking things. We can even write new tests as we find new issues, and then write the code that makes those tests pass.

Testing makes life easier in the long run#section4

It’s easy to look at all of this and say, “Wait, you want me to write more code to do the same job?”

The thing is, there are a few inescapable facts of life about Making Things On The Internet. You will spend time designing an approach to a problem. You will test your solution, whether by clicking around in a browser, writing automated tests, or—shudder—letting your users do your testing for you in production. You will make changes to your code, and other people will use your code. Finally: there will be bugs, no matter how many tests you write.

The thing about testing is that while it might require a bit more time at the outset, it really does save time in the long run. You’ll be patting yourself on the back the first time a test you wrote catches a bug before it finds its way into production. You’ll be grateful, too, when you have a system in place that can prove that your bug fix really does fix a bug that slips through.

Additional resources#section5

This article just scratches the surface of JavaScript testing, but if you’d like to learn more, check out:

  • My presentation from the 2012 Full Frontal conference in Brighton, UK.
  • Grunt, a tool that helps automate the testing process and lots of other things.
  • Test-Driven JavaScript Development by Christian Johansen, the creator of the Sinon library. It is a dense but informative examination of the practice of testing JavaScript.

About the Author

Rebecca Murphey

Rebecca Murphey is a senior staff software engineer at Bazaarvoice and a frequent speaker at conferences around the world. She’s also the creator of the TXJS conference, the creator of the js-assessment project, the host of the TTL Podcast, and a contributor to the book Beautiful JavaScript. She blogs at rmurphey.com, tweets as @rmurphey, and lives in Austin, TX, with her partner and their son.

32 Reader Comments

  1. Another great article with perfect timing, when “Write Unit Tests” just so happens to be near the top of my TODO list. Thanks ALA!

  2. Great article. I think its important to note that a lot of the success in this kind of testing is by partitioning the web page into small, independently operating ‘modules’ of functionality. Doing so not only makes the code small, removes complexity and reduces the cost of testing that ‘module’ but also affords the necessary isolation… isolation for the purposes of integration testing yes but also for the purposes of design, QA, user testing, and delivery (really the full life cycle can be completed on each) of that ‘module’ independent of the other ‘modules’ of the application. For example, its common practice to want to design all the ‘modules’ up front to see how they’ll layout and interact but there is value in building the ‘modules’ independently. The completion of each doesn’t just mature the app incrementally but also the understanding and long term success of the steps involved in the creation of each ‘module’. The great thing is that the ‘modules’ can be in various stages in the life cycle and you can correct course as necessary, and if you do not much lost. We’ve had a lot of success with developing, testing and delivering like this. And while the app, not be entirely complete at any given stage, its always operational and IMHO shippable.

  3. Andrew, so glad to hear that this was timely! I think a whole lot of people are finding that tests are going to be an important part of their workflow going forward, so hopefully this helps you get started. You may also want to check out the talk I gave about this at the 2012 Full Frontal conference.

  4. Schon, I can’t agree enough with what you said. The key to successful applications, no matter the language or whether it’s in the browser or somewhere else, is to solve lots of small problems, rather than one enormous, tangled problem. For client-side applications, the smallest unit of functionality is often something that you can draw a box around in the visual presentation of the application. I’ve found that when I approach client-side apps in terms of lots of tiny boxes that operate independently, glued together by application code that knows very little about how any given tiny box works, then life is simpler indeed 🙂

  5. I agree that the second coding style presented in the article is a huge improvement. For complex Web Apps however, you might consider a client-side MVC framework like AngularJS, which incidentally has built-in support for unit tests.

  6. mb21, agree that moving to an MV* framework such as Angular, Ember, Backbone, etc. will really help with the testability of an application. What I have found, though, is that a lot of people can’t make that leap from day 1, and figuring out what the “baby step” is to start incorporating testing proves difficult. Writing code in the style I presented is the first step, I think, and a much more realistic one for entrenched projects that can’t easily have some Angular sprinkled on them. Once you’ve written code in this style, though, it becomes much easier to see how a more robust tool could be incorporated. What I hate to see is people who dive into Backbone, Angular, or another more complex tool without understanding *how* it will help them solve their problem, or without understanding why or whether it’s even the right tool for the job. Learning a new coding style can help smooth that process.

  7. I don’t understand what the colored bars that appear under the heading “Organizing our code” refer to?

  8. Great article and fantastic ideology! The only thing that bugged me was that the code samples don’t have syntax highlighting, which makes for a terrible reading experience. Of course that’s a A List Apart bug and not related directly to this article – just thought I should put it out there.

  9. Thank you for a fantastic article! Have used QUnit quite a bit and just this week trying out Mocha/Chai with Grunt. Seeing a practical example of how to refactor/approach unit testing and not just write unit tests themselves is incredibly helpful!

  10. Bob, apologies for the confusion regarding the colored bars. The idea was to show how different pieces of the original code were responsible for different things, and how all of those responsibilities were intermingled with each other.

  11. It’s very rare to see articles that discuss testing (particularly browser JavaScript) in such an approachable way. I think this often happens because example code is written in a testable way, however provides no bridge to help fix existing problems. It is refreshing to see an article that makes this connection.

    Great post, Rebecca!

  12. Aw thanks Nate 🙂 When I started thinking about my presentation last Fall, I realized that if I started with the tests themselves, a lot of people would go home thinking “that’s nice, but my code doesn’t look like that and I can’t imagine how to get there.”

    On my personal path of learning to be good at JavaScript, I found that once *I* understood the stuff I explained in this article, thinking about testing — and writing JavaScript in general — just got vastly easier. I wanted to write it down in hopes that others might have some of the same “ah ha!” moments that I did.

  13. Craig, there’s not a whole lot that’s different about testing with RequireJS/AMD. If you take a look at the repo for the project in this post, you’ll see that it makes use of RequireJS. Generally it’s just a matter of having a test-specific config that points to the test files and the core files, and the rest is pretty straightforward. For cases where you need to mock dependencies, Squire.js is a very useful and actively developed tool.

  14. Any chance you could publish the code? I’ve tried to follow along with this and Mocha just isn’t playing. Their site doesn’t particularly help because its favoured “describe” syntax seems unintuitive, is explained badly, and doesn’t match your test construction. When I try running mocha using “setup” and “test” it just complains that they’re not defined…

  15. Great article.

    Biggest problem I’ve had with searching on Javascript testing is they are lots of examples on how tests work, various frameworks, assertions with trivial examples etc etc which is all easy but non seem to go further in to the real world on how to design frontend components as you described in a testable way.

  16. Performance is typically the reason I segregate “unit” tests from “functional” tests
    – by melissa from LionLeaf

  17. Love the article! LOVE IT!! That is probably the best piece on JavaScript I have read in recent months. I also totally agree with your comments that people jump into Backbone etc without understanding, not a good idea. IMO this article is a must read for someone with (lower) intermediate JS knowledge like myself. also love the colored bars explanation. I had no problems understanding but maybe small “legend” would help as I understand if someone reads it in a hurry might get confused with colors

  18. Gianna. I agree that Clifford`s st0rry is shocking… on sunday I got a great new Acura since I been making $7686 this-past/month an would you believe 10-k last-munth. it’s by-far the most-financially rewarding Ive had. I actually started five months/ago and right away began to earn minimum $84 per/hr. I follow instructions here,, http://m3mi.com/3621CHECK IT OUT

  19. Beautifully articlated! Recently, I have been authoring a light weight Dependency Injection framework called WinterJS with the primary objective to allow developers to author testable code. It would be great to get some feedback on this.

  20. This is a well written article, but I feel that if you are going to write an article about javascript you should write the examples in javascript. Not everyone is going to be using the same framework/library as you, so for universal understanding it would be best to write examples in vanilla javascript.

  21. How to distinguish the private methods or public methods? Although some methods have ‘_’ underline tag, but in fact, all of these are public method. For example “SearchResults.prototype._handleClick = function (evt) {**}”. Can you give some suggestions, thank very much!

  22. I think what has been demonstrated here is something pragmatic and practial not entirely elegant. Because, the test codes here are kinda unit & integration test married together. I prefer to write my unit tests without DOM and CSS selectors; this needs dom/css isolated code, **mostly**. But, I still could not find a great way to do that. If any of you already did that please let me know.

  23. Thanks for the great article!

    My only problem is that it’s not only obviously a good intro to testing, but also a good introduction to deferreds, RSVP and how to better structure your code. It’s a bit of a jump from the procedural way (I would have written it) to the uncoupled way.

    I’ve done my own non-backbone version and got it to a point where I understand all the parts except for what’s going on here

    Anyone get why this is needed? I’ve put a break on it and it doesn’t seem like the contents of the ‘if’ ever gets run.

    Also, does anyone understand why ‘ajax’ works but using ‘get’ instead returns an empty results object?

     {"results":[]}

    btw, anyone using input type ‘search’ instead of ‘text’ might find that 2 results are returned, the first being empty. In Chrome at least. Was driving me mad for a quite some time!

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career