Declarative Dependencies for Unit Testing Node.JS Services

Emil Ong
Haus Engineering Blog
6 min readMar 30, 2016

As a former Rails & Java developer and a relatively new server-side Javascript developer, there are many things I love about my new environment, but a number of things I have missed as well. As a fan of TDD and unit testing in general, I found it hard to figure out how to inject dependencies for unit testing with CommonJS & Node’s tight filesystem binding. This article discusses using Dependency Injection containers to get around that problem.

Introducing Dependency Injection Containers

Let’s assume that our project is laid out as shown below. It should look familiar if you’ve used Rails or other MVC frameworks. We attach routes in server.js to controller actions, the controllers may call into services for complex actions, and the services call into models as domain objects and persistence. The models themselves may have some additional logic specific to their domain. So the question arises, how do we reference code from one module to another, given this or a similar filesystem layout?

app/
controllers/
models/
services/
server.js

Node.js offers us the CommonJS convention for loading relative modules (and files), so if I know where to find a file that contains the code I’m looking for, I can do that quite easily. Thus, I could have a service that looks like the following:

This doesn’t look too troublesome, but it creates a tight binding between a piece of code’s dependencies and their location on the filesystem. As you’re looking at the application code alone, this seems like mostly a non-problem as you’ll decide once (or at worst a few times) where you want your models, services, whatever to live in your repo, so you’re binding to something that won’t change… but what about testing?

If you want to unit test your service and need to inject a test double of some sort to test interaction, you’re in a tough spot with this filesystem binding.

Moreover, notice that we’re creating and configuring an API client here. If we want to stub/mock/fake/whatever this client for testing, we’re going to have to figure out some other option for injecting it into the service.

DI Container Requirements

Dependency injection is a popular concept in the Java world and is generally implemented with DI containers. They can help us here in Javascript as well.

They should do a few things:

  • Know how to create a resource
  • Keep track of instances of resources
  • Inject a dependent resource when creating a new resource
  • Provide a declarative way to specify dependencies

If we had such a container, it would solve the test issue we saw above and open up a lot of other possibilities at the same time.

In the case above, if the container knew how to create or find the Content model, we could declare it as a dependency of the service rather than finding it imperatively from within the code. We could then declare the service a dependency of both a controller and an asynchronous task to run in something like Kue, inject the controller into the router, then mount the router into an Express application for which we’d start a server.

Each of these elements is a small, well-contained, focused piece of code. We’re able to compose them all together at will and replace their dependencies for unit testing simply by registering an appropriate double in a test container.

Moving from Imperative to Declarative Dependencies

The promise of a DI container hopefully sounds good at this point, but how do we actually use one?

I chose BottleJS as the container in my project, but again, any container that performs the functions mentioned in the previous section should do fine. (There isn’t a huge number of them in the Javascript world at the moment, but hopefully that will change.)

I developed the following convention for all my files that produce (and consume) resources in the DI container:

There are two main parts to the file, the provider code and registration with the container. We’re still using CommonJS, but just so that where ever we require this file, it will produce a function that takes the DI container and register our provider with it. Thus each file becomes its own little module and doesn’t care what else is in the container (besides its dependencies), who provides it, or who consumes their service.

Notice that we’ve also decoupled the PdfApi client from creating the service and injected it via the container. Again, this isn’t the only way do such decoupling, but it does work quite nicely in this approach.

Bootstrapping the application

Before we move on to testing, let’s just see how we actually get the code loaded for production use. Essentially all we need is to create a dependency injection container, load it up with all the resources in the application, pull out the application’s entry point and call it.

We start by creating a new container and filling it with everything we find in the app/ tree of the project. None of the resources is actually created at this point, but the container now knows how to create them. The order of loading doesn’t matter and if we drop a file anywhere in that tree, it will provide and consume to and from the container without knowing anything about the rest of the file layout.

Finally, we register an Express app in the container so we can inject it too with dependencies, then we have just one line that grabs the app out of the container and starts a server. This file is the only place in the whole app that needs to be concerned with the filesystem layout of the application; we don’t need to know where any other files are from any other place in the app!

Unit Testing with Declarative Dependencies

Let’s get back to how DI containers can make testing easier, starting with unit testing.

Unit testing validates that one small piece of code behaves as it should, only relying on the interfaces of other code, not their internal behavior. Having a DI container and putting your code into it is already a good first step in delineating what your dependencies are! It’s still up to you to keep the code small and modular, but at least you’re encouraged to be declarative of what you need.

Here’s an example of the test for the Pdf controller we reference in the app bootstrapping example above:

I’m using Chai and Sinon here, but the principles should apply to any test framework. We start out by creating a totally empty container, just for this test, then load the code for just the subject under test into it. From there, we have completely isolated our code and have control to be able to inject fake versions of its dependencies. In this case, we’re testing how the code deals with extracting data from the HTTP request, invoking a dependent service and dealing with its output, and how it sends and formats the response.

I should note that this example has a bit of ugliness in the require on line 17. We still need to know where the code is for the controller in this case, but for a unit test, its test is tightly bound to the unit it’s testing, so hopefully you’ll forgive this one line.

Here’s an example of what the code for the controller might look like to give you an idea of the dependency declarations. (Note that this example does not yet have the error handling code that tests in ellipses above imply… :))

Integration testing

DI containers are not restricted to unit tests, though they do particularly shine there. Similar to the points above about creating entry points into services for both controllers and asynchronous tasks, a DI container also lets you call your code, fully set up, from a test runner as well.

In fact, you don’t have to call your code from the root application alone, e.g. making HTTP calls locally etc. (although that can be useful at times). Rather, you can call at any point in a subtree of your application. For example, you could call a service with all its dependent models fully loaded and writing to the DB. You don’t need to call it via a controller or async task.

Conclusion

DI containers are a helpful tool for being explicit about dependencies and interposing various ways of calling different parts of your application, be it from HTTP, an async task, test, a Gulp/Grunt task, or whatever. The layout and tool choices I presented throughout are separate from these guiding principles, but hopefully give you an example to understand how this might be applied.

If you’re interested in working with us, discussing issues like these and others, please check out our jobs page and get in touch!

Thanks to Ali Faiz and Jonathan Otto for providing feedback and to Ali for pointing me to proxyquire as related work.

--

--