Alpha Hydrae

Coding on the edge of the web...

Node.js Dependency Injection

Early in my Node.js adventures, I started asking myself how to write real unit tests with mocked dependencies. Let’s take a slightly modified Hello World example:

1
2
3
4
5
6
7
8
9
var http = require('http');

exports.start = function() {

  http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
  }).listen(1337, '127.0.0.1');
};

Imagine this is a unit of your app responsible for creating an HTTP server and listening on port 1337. It exports a start method which does this.

To write isolated unit tests, you need to mock the server. Your tests will run much faster against a mock HTTP server. It makes little difference for this small example, but the gain will be significant for a more complex module that makes many HTTP calls.


Using a mock library for modules

I found several Node.js modules that can do this, such as sandboxed-module or mockery. For example, sandboxed-module can require your module in a way that allows you to supply mocks for its own requires:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var sandbox = require('sandboxed-module');

// Create a mock http module.
var httpMock = {

  createServer : function() {
    return createMockObject();
  }
};

// Require the module you wish to test with sandboxed-module.
var module = sandbox.require('./module', {

  requires : {
    // Use a mock HTTP server.
    http : httpMock
  }
});

// Test the module with mocked dependencies...

When the module you’re testing requires http, it will be given the mock instead of the real thing.

I was happy with this until I found out that it sometimes breaks things. I recently had tests failing for no apparent reason with sandboxed-module requiring one of my modules that uses async. It seems that they don’t play nice with each other.

Manual dependency injection

You can always roll your own dependency injection. Note that there are dependency injection modules out there. I just didn’t deem it necessary to restructure my application to use those when it’s so easy to do.

Here’s how you could do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// To use this module, call the exported inject function
// with an object of dependencies.
exports.inject = function(deps) {

  var http = deps.http;

  // Construct and return a module with the provided dependencies.
  return {

    start : function() {

      // The core code hasn't changed. It's just using the provided http
      // module which could be either the real module or a mock.
      http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('Hello World\n');
      }).listen(1337, '127.0.0.1');
    }
  };
};

By passing dependencies to the inject function, you can easily swap implementations:

1
2
3
4
5
6
7
var myModuleForProduction = require('./module').inject({
  http : require('http')
});

var myModuleForTesting = require('./module').inject({
  http : httpMock
});

It can also be made easier to use by including default dependencies in the inject function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
exports.inject = function(deps) {
  deps = deps || {};

  // Use the real http module if none is provided.
  var http = deps.http || require('http');

  return {

    start : function() {

      http.createServer(function (req, res) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('Hello World\n');
      }).listen(1337, '127.0.0.1');
    }
  };
};

This simplifies production usage:

1
2
3
4
5
var myModuleForProduction = require('./module').inject();

var myModuleForTesting = require('./module').inject({
  http : httpMock
});

May your unit tests be swift.

Meta

Comments