Dependency Injection in node.js

December 17, 2010

Dependency injection is a handy technique for writing testable code. If you’ve used Spring in the Java world, you’re probably familiar with it. Put simply, it means that code should not be responsible for instantiating the things it depends on – these dependencies should be passed in by the calling code. That means you can pass in mock versions for testing.

In node.js land, we use require to bring in external modules. For example:

var http = require('http');
exports.twitterData = function(callback) {
  http.createClient(... blah...);
}

This module is hard to test without needing internet access, which may not be available if your tests are running on a build server behind a corporate firewall. Besides, you don’t want your tests calling twitter all the time.

What you want to be able to do is replace http with a mock version, so that you can verify that your module makes all the right calls without actually needing access to the internet. There are a couple of ways to do this, like messing about with require.paths to bring in mock modules. Here’s the way we decided to do it.

First we write our modules in this style:

module.exports = function(http) {
  var http = http | require('http');
  // private functions and variables go here...

  //return the public functions
  return {
    twitterData: function(callback) {
     http.createClient(...etc...);
    }
  };
}

In normal use, we’d require our twitter module like this:

var twitter = require('twitter')();

In our tests, we’d require it like this:

var mockHttp = { createClient: function() { assert(something); } };
var twitter = require('twitter')(mockHttp);
//do some tests.

It seems to be working fine for us so far, and having that first line of your module tell you explicitly what the dependencies are is quite handy as well. If you want some real-world examples of this, take a look at the log4js-node source.

Advertisements

6 Responses to “Dependency Injection in node.js”


  1. Nice idea but as a drawback now you need to remember what modules are “dependency injection aware” and what are not. That breaks encapsulation since modules that are easier to test follow different rules than other modules.

    • csausdev Says:

      I agree that it does place a slightly higher burden on the caller of the module (they have to remember the “()” after the require). But there are plenty of modules out there that return a function so that’s not so unusual, and by setting up sensible defaults you can avoid the need for the caller to know all the dependencies. I would rather have well tested code, and for my tests to have no side-effects.

  2. Bruno Windels Says:

    I like to add a method inject to the object I am exporting. It is totally invisible to code that actually uses your module and is really easy to use from unit tests. Especially if you want to change the mock multiple times inside a test.


  3. I’ve created a module which handles this, without the caller interface having to change. injectr (https://github.com/nathanmacinnes/injectr) uses node’s vm library to run the file under test in a sandbox, with a fake version of require which you can pass your mocks into.

  4. csausdev Says:

    Thanks Nathan – we’ve been using felixge’s sandboxed-module (https://github.com/felixge/node-sandboxed-module) which does the same thing (I think) as your module.

  5. jhnns Says:

    I’ve also written a module to accomplish this, it’s called rewire (http://jhnns.github.com/rewire/).

    I’ve been inspired by Nathan MacInnes’s injectr but used a different approach. I don’t use “vm” to eval the test-module, in fact I use node’s own require. This way your module behaves exactly like using require() (except your modifications). Also debugging is fully supported.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: