A Gentle Introduction to Javascript Test Driven Development: Part 2

Written by James Sinclair
on the 17th April 2016

This is part two of a three-part series introducing my personal approach to JavaScript TDD. In the last article we began creating a small application that loads image data from the Flickr API and displays it in a web page. We began by setting up modules and writing simple unit tests using the Mocha framework. In this article we’ll look at how to test asynchronous network calls (also known as AJAX).

Testing asynchronous network (AJAX) calls

In the last article I joked that I was procrastinating about testing the code where we call the Flickr API. And not without reason. I was procrastinating because testing network calls is a little bit complicated. There are three things that make this tricky:

Testing an API call needs access to the network, which I can’t always guarantee;

Network calls in JavaScript are asynchronous. This means that when we make a network request we interrupt the normal code flow; and

The results from the network call change often. This is the whole point of the network call—but it makes it somewhat difficult to test.

I could go ahead and just write a test that makes the network call and checks what comes back, but this would have a some drawbacks:

The data coming back from the live Flickr API changes all the time. Unless I’m careful about how I write my tests, they would pass for maybe a minute before new data broke my test.

Making network calls can be slow, and the slower my tests, the less fun TDD becomes.

Doing things this way needs an internet connection. I regularly find myself writing code on a bus, or a train, or some other location without (fast) access to the internet.

So, I need to think carefully here about what I want to test. I will create a method called fetchFlickrData() that grabs data from the Flickr API. For this to work, I need to make a network call. But to make a network call, I will be calling some sort of API. The simplest API for this purpose would jQuery’s getJSON() method. getJSON() takes a URL and returns a Promise for the JSON data. If you’re not familiar with Promises, it’s worth taking a moment to get the basic idea.1

Now, to handle this neatly, I need to think like a functional programmer. Network calls involve side effects, making my function impure. But, if I can isolate the impure part (i.e. getJSON()), then I have a pure, testable function. In other words, what if I made getJSON() a parameter that I passed into my function? The signature might look something like this:

fetchFlickrData: function(apiKey, fetch) {
// Code goes in here
}

In the application code, I would pass $.getJSON as the fetch parameter (more on that later). In my test though, I can pass a fakegetJSON() method that always returns a promise for the same data. Then I can check that my function returns exactly what I expect, without making a network call.

The other thing that is tricky about network calls with JavaScript is that they are asyncrhonous. This means that we need some way of telling our test runner (Mocha) to wait until all the tests finish. Mocha provides a parameter to the it() callback called done that allows us to tell Mocha when the test is complete.

I’ve been a little bit clever here, and included an expect() inside the fake fetcher function. This allows me to check that I’m calling the right URL. Let’s run the test:

The cat is sad because there is no function yet.

Stubs

Now that I have a failing test, let’s take a moment to talk about what this is doing. The fakeFetcher() function I’ve used to replace $.getJSON() is known as a stub. A stub is a piece of code that has the same API and behaviour as the ‘real’ code, but with much reduced functionality. Usually this means returning static data instead of interacting with some external resource.

Stubs can replace many different types of code besides network calls. Most often we use them for things functional programmers call side effects. Typical stubs might replace things like:

Queries to a relational database;

Interaction with the file system;

Accepting user input; or

Complex computations that take a long time to calculate.

Stubs don’t always have to replace asynchronous or even slow things. It may simply be a piece of code you haven’t written yet. A stub can replace almost anything.

Stubs are an important tool for TDD. They help us to keep tests running fast so our workflow doesn’t slow down. More importantly, they allow us to have consistent tests for things that are inherently variable (like network calls).

Stubs do take a little bit of effort to use well though. For instance, using a stub meant adding an extra parameter to the fetchFlickrData() function. However, if you are using a slightly functional-flavoured style of programming, then you will be thinking about things like side effects and pure functions anyway. I would also argue that making your code testable (whether that’s using stubs or not) is usually worth the effort.

But enough about stubs—back to the code…

Running the tests, I get an error, but that’s still a sad cat (red), so I can write some code. In this case, returning the expected result isn’t that simple. I have two expect() calls in there, so I have to call the fetcher function as well as return a promise for the data. In this case it’s easiest to write the general code straight-up:

Running the tests again, everything still passes. But I would also like to refactor my test code. Mocha actually provides two ways to handle asynchronous code. The first is the done() function as we saw before. The second is specifically for Promises. If you return a Promise from your test, Mocha will automatically wait for it to either resolve or reject:

Running my refactored code, the tests still pass, so it’s on to the next step.

Building up

At this point, I need to stop and think. There is one final thing to test before I can declare the FlickrFetcher module done: Do the pieces fit together OK? Can I make a network call, get back the results, and transform them into the format I want? It would be most convenient if I could do all this with one function.

Running the test again, my test passes—happy cat (green). So it is time to refactor. But, since this function is just three or four (depending how you count it) function calls, there’s not much to refactor.2 So, for the moment, I have completed my first module.

So, what have we covered? In this article we covered two main topics: Testing asynchronous code and using stubs to standardise things like network calls. The next article will focus on working with HTML and the DOM.

Yes, if you‘re using a version of jQuery earlier than 3.0, it’s only Promise–like, but either way it's close enough for our purposes here. ↩

It could be slightly more terse if I was using functional programming techniques, like a mapWith() function and/or partial(), but to use either of those I would have to introduce a dependency or write my own implementation. ↩