Sometimes unit testing is a trivial affair, but that’s not always the case. In the past I found that I struggled when testing node.js applications built using express - specifically when it came to unit testing my routers. This isn’t a flaw with express, it’s simply the fact that it’s not immediately obvious what the best approach is, particularly when stubbing out dependencies is a requirement.
Supertest to the Rescue! Sort of.
Thankfully, the supertest library is available to assist us in our testing endeavours! Here’s an example that uses supertest:
The example above demonstrates that we can pass our express application server to the supertest library, and thereby run tests against the server. This is awesome, but doesn’t come without challenges. For example, what happens in the scenario where we need to stub out dependencies that hit a live system, or that cause our tests to have a needlessly long execution time? We could use proxyquire, but that only allows us to stub dependencies one level deep. If we did that we would be stubbing out our routes, and not their dependencies that actually make the calls! Setting environment variables is another approach that might grant us some control, but this would become a deeply tedious manner of testing different paths throughout the codebase.
Inversion of Control
I’ve found that using inversion of control to mount express routers (instances of express.Router) to an express application object has turned out to be a fantastic method to simplify unit testing of my routes, and also make them easily configurable since I can pass variables into the creation of a route. What does this all mean? It’s best demonstrated using an example and a short overview, so bear with me.
Typically in an express application you might bind routes to an application object in your entry point file. This is the approach that’s often presented online in examples and tutorials, and is demonstrated below.
If we were to invert this, we’d create a small module that exports a function, pass the express object into the function, and have the function bind an express.Router to the passed object. Here’s an example of that in action.
The difference is subtle, but it significantly improves our testing ability and makes routers configurable.
- The full mount path is defined on the router e.g “/users/:username” so we can detect if the route is mounted incorrectly in unit tests.
- Each invocation returns a unique router instance.
- Dependencies can be injected. This means a router is configurable, and reusable.
- Easier to mount routers uniquely to many express instances if required without clearing the require cache.
- Testing is simplified since we can create a unique router prior to each test without clearing the require cache.
Here’s an example module that creates a router and binds it to an injected express application.
Below is a snippet that demonstrates how we can test our route in isolation from other components. It might appear to be complex at first glance, particularly if you haven’t used sinon or proxyquire in the past, and that’s fine! Take a read of the code and comments, then we’ll cover some of the statements in a little more detail below.
So what’s happening in this test? The supertest calls, that’s anywhere you see request.METHOD, are pretty digestible; you state the URL you want to make a request to, and then state your expectations. The real magic happens in the beforeEach. In this function we require our module, stub out it’s dependencies using proxyquire, create an independent express application instance, and pass that express instance to our module. This means we have a minimal express application that contains the current route we need to perform tests on, and no more. Had we simply required our application entry point we’d potentially have to ensure a number of environment variables are configured, deal with startup tasks, and find a way to stub out dependencies that are nested more than one level deep.
proxyquire is a replacement for the require statment (only for testing!) that allows you to provide a second argument that contains a hash, this hash maps require calls in the file being required (the first parameter) to stubs you’ve defined (the second parameter). Besides making testing easier, proxyquire also enables a developer writing tests for a route to continue doing so, even if a dependent module is unfinished. In our example above we don’t care if the users module is finished or not, we just need to know what its interface and return values will look like.
Using sinon to create stubs is not required, but is very useful since it’s a thoroughly tested, standard library that’s designed specifically with testing in mind. You could write your own stub functions, but being able to write stub.returns(‘hello, world’) is more concise, standardised, and immediately comprehensible.
As demonstrated it is relatively easy to test express routers in isolation, it just takes a little setup initially. In an agile environment this is beneficial since it reduces the dependency of one developer on another’s code while working on parallel stories and also makes it possible to produce more robust, tested code. Using a code coverage tool such as istanbul will allow you to immediately see where you’ve missed test coverage in your unit tests too.
The image below is an example of the test output for the code above.