Menu

Writing Unit Tests for Node.js Application

Given the widespread adoption of node.js it’s surprising that there is not much synthesized information about the specifics of writing unit tests on this platform. Recently I open sourced Nagual, HTTP simulator for faster and reliable automation tests. these are the challenges I faced writing unit tests for node.js application.

Linter

Linters are part of the three pillars of automated tests. It does not make sense to not use one, given how easy it is to setup. They are fast and incredibly useful when working with interpreted languages. Linters will catch some corner cases and bugs that unit tests will miss. For JavaScript you have two main options (excluding the father of JS linters - JSLint as everyone finds it is too opinionated) - JSHint and ESLint. ESLint is newer and is what the cool kids are using these days.

Test Framework

There are couple of actively supported unit test frameworks for node.js. I choose Jasmine, mainly because it has all the basic functionalities built in. For example it has its own spy functionality:

spyOn(foo, 'setBar');

If you choose any other unit test framework, say mocha, you need to use external spy library like Sinon.JS.

var sinon = require(‘sinon’);
sinon.spy(foo, "setBar");

It’s not a big deal, but I’m more productive when all the parts needed are in the same package. No need to do research, pick and choose and so the paradox of choice does not kick in.

Directory Structure

Whatever unit test framework you choose, it’s widely adopted to mirror the directory structure of your main application in the tests directory.

As you can see, the directory tests/source/contactTheRealServer contains all the tests for the four functions exposed in contactTheRealServer module. The tests names need to end with ‘Test’ or ‘test’ so that Jasmine can recognize and run them.
This is how to config file looks like:

Small Modules

Setup your linter to alert you if function complexity is above 10. This will make you break big modules into smaller ones which are easier to unit test. The number of tests you need to write for a function is the same as the cyclomatic complexity for that function. When I started Nagual as a proof of concept, it was one big function so to unit test it, I had to break it down to smaller pieces.

You can also export only the functions that the other modules need. This is similar to making a method public in the OO languages. If you do not export a function to the outside world, there is no way to test it (directly).

Writing basic test cases for node.js application is pretty much like writing them in other languages, so I'll focus only on the corner cases bellow.

Spying on Imported Functions

You want to write a unit test that exercises handleRequest(). In particular you want to know that returnResponse() has beed called. With the current code you can’t do this because the unit test does not have any control over returnResponse() function. One way to do this is to not use returnResponse() directly but instead assign it to module.exports and use it though that interface. Here is how the code will look like after the change:

Stubbing require()

In Nagual’s case there is a function that loads objects from the file system via require(). You can not directly control require() but you can wrap it in a function that you can overwrite and use this new function to load your objects (the full example is here):

It case the request is either GET or HEAD, there is no body and it’s trivial to test. In case the request has a body (POST, PUT or DELETE methods), an event listener is involved — the on() function that listens for ‘data’ and ‘end’ events. Testing is not straight forward because these are asynchronous operations. One way to test is the following:

Node.js has a stream called PassThrough (all streams are instances of EventEmitter). It acts as a test dummy that can be used to emit ‘data’ and ‘end’ events. It is used instead of the real request. The last assertions makes sure that the POST buffer is assembled correctly (it also means that the ‘end’ event is emitted).