I’ve been doing a lot of work with node.js recently, and since I care about testing and test coverage quite a bit, I’ve had to solve the problem of how to test classes that have what I’d call “fake-worthy” dependencies like database clients, or the file system, or network i/o (to name only a few). When I call a dependency “fake-worthy”, I mean that it’s often easier/better/faster to use a fake version of it in the tests than the real thing for a number of reasons that I won’t try to get into here.

I’ve used IOC containers on other platforms, and I’ve seen a number of ways to make require() do different things depending on whether you’re in a test environment or not. In the end, I’ve decided to just follow a simple pattern that is extremely low-tech compared to anything else I’ve seen. It should be usable in browser javascript just the same.  Here’s the skinny:

TIP #1: Pass in any fake-worthy dependencies as optional parameters.

Yes, I will change application code to make it more easily testable.
For example, if I want to test this code:

function addUserToDatabase(user){
 var db = new Database('127.0.0.1', 'someapp');
 db.insert('users', user);
}

I’ll want to test that addUserToDatabase() does its database work correctly, but I won’t want my test suite to actually hit the database:

It could instead be written as:

function addUserToDatabase(user, db){
 db = db || new Database('127.0.0.1', 'someapp');
 db.insert('users', user);
}

This let’s me do:

addUserToDatabase({name : "Joe"});

…which instantiates a new database object, because when you don’t pass a db, one gets created for you. But it also lets me do :

var fakeDB = {
 insert : function(table, obj){
   assert.equal(table, 'users');
   assert.equal(obj.name, 'Joe');
 }
}
// inject fakeDB via the second param
addUserToDatabase({name : 'Joe'}, fakeDb);

…which lets me pass in a fake database object for the purpose of testing that addUserToDatabase() calls the database correctly.

TIP #2: Don’t do anything fake-worthy in a constructor

It’s often tempting to do all kinds of initialization in a constructor so that the object is ready to use by all methods right after construction, but that can lead to problems.

For example, this code:

var CarModel = function(){
 this._db = new Database('127.0.0.1', 'someapp');
 this._db.connect(); // makes a network call to the db
}
CarModel.prototype.add = function(car){
 this._db.insert('cars', car);
}

If you don’t want to use the optional parameters trick for this constructor, you have a second option because you’re being object-oriented. You can simply call connect() outside the constructor:

var CarModel = function(){
 this._db = new Database('127.0.0.1', 'someapp');
}
CarModel.prototype.add = function(car){
 this._db.connect(); // makes a network call to the db
 this._db.insert('cars', car);
}

This makes it possible to inject a dependency using a property of the object, without changing any function signatures. Here’s an example test doing just that:

var connectWasCalled = false;
var insertWasCalled = false;
var fakeDB = {
 insert : function(table, obj){
   assert.equal(table, 'cars');
   assert.equal(car.name, 'Tesla Model S');
   insertWasCalled = true;
 },
 connect : function(){
   connectWasCalled = true;
 }
}
var carModel = new CarModel();
carModel._db = fakeDB;  // injection!
carModel.add({name : 'Tesla Model S'});
assert(connectWasCalled);
assert(insertWasCalled);

TIP #3 Use Lazily Initialization Methods

Removing fake-worthy activities from the constructor is what I’d call “Lazy Initialization”, meaning that you don’t get the dependency ready for use (db.connect() in our example) until the last reasonable moment. When you want to have a dependency lazily initialized for other methods as well, it can help to create a lazy-initialization method instead. Imagine we add a new ‘remove()’ method to our CarModel:

var CarModel = function(){
 this._db = new Database('127.0.0.1', 'someapp');
}
CarModel.prototype.add = function(car){
 this._db.connect(); // makes a network call to the db
 this._db.insert('cars', car);
}
CarModel.prototype.remove = function(name){
 this._db.connect(); // makes a network call to the db
 this._db.remove('cars', name);
}

What I would do here instead is refactor to this:

var CarModel = function(){
 this._db = null; // just to be explicit
}
CarModel.prototype.db = function(){
 if (!this._db){
   this._db = new Database('127.0.0.1', 'someapp');
   this._db.connect();
 }
 return this._db;
}
CarModel.prototype.add = function(car){
 this.db().insert('cars', car);
}
CarModel.prototype.remove = function(name){
 this.db().remove('cars', name);
}

To test add() in this scenario, I’d just substitute the _db property with our fake:

var connectWasCalled = false;
var insertWasCalled = false;
var fakeDB = {
   insert : function(table, obj){
   assert.equal(table, 'cars');
   assert.equal(car.name, 'Tesla Model S');
   insertWasCalled = true;
 }
}
var carModel = new CarModel();
carModel._db = fakeDB;  // injection!
carModel.add({name : 'Tesla Model S'});
assert(connectWasCalled);
assert(insertWasCalled);

These three patterns have made it so that I can easily deal with any fake-worthy dependencies by either monkey-patching them in or passing them as optional arguments. It doesn’t feel as clean as it could be at first because:

  • I generally try to avoid monkey-patching.
  • I usually try to avoid test-only use-cases in my application code.

I’ve decided to trade those two values away for simple testability though. While other people can wrestle with mocking frameworks that do strange things with require(), or try to devise an IoC container for javascript, I think I’ve found a methodology that won’t actually add as much complexity in the long run.

Because Javascript objects leave their internals open for access (they have no private/public access modifiers), I consider mucking with the internals to be allowed for testing purposes. I generally consider it allowed for any purpose really, because we’re all adults here, but of course for other purposes, the developer needs to know he does so at his own risk.

Once you embrace this sort of philosophy, you realize Javascript has a built-in DI framework.

7 thoughts on “Javascript has a Built-In Dependency Injection Framework

  1. Nice article, but there are better ways for unit testing these methods, simple call prototype methods with different context, can use sinon too for mocks

  2. Fantastic idea. I’ve never seen that method used before. It’s not obvious to me why it’d be *better* but I’ll give it a shot first. It’s obviously just as good though. Thanks for the tip.

    I just wrote manual mocks for the example. A mocking framework like sinon would’ve been better, but I didn’t want to complicate the explanation.

  3. what do you think of this approach?

    // server.js
    var config = require(‘./config/’ + environment + ‘.js’);

    // config/test.js
    config = {
    db: require(‘../mockDb.js’)
    }

    module.exports = config;

    // config/production.js
    config = {
    db: require(‘../db.js’)
    }

    module.exports = config;

  4. An important thing to remember about both IoC containers in general, and Require.js, is that they are about more than just dependency injection. As you said, dependency injection sort of comes for free in JS, for anything that’s either publicly exposed or created through a publicly defined function. Ruby has the same facility. But there are other reasons you might want to use Require.js, or a more full-featured IoC container.

    Requires.js is an implementation of Asynchronous Module Definition (AMD) which offers a host of other benefits besides injection such as asynchronous loading of modules, deferred execution of module definition, and caching of module definitions to avoid redundant execution.

    IoC containers typically also offer a number of features surrounding lifecycle management. Require.js essentially assumes that all modules are singletons. If you need a factory, then the module hosts the factory object, and the things being created are produced from within. IoC containers usually go a step further to give you a much finer grain control over what is created, how, and how often. This will all only continue to become more useful as client code becomes larger and more complex.

  5. @Michael J. Ryan: True, but dependencies with asynchronous interfaces can be injected just the same. How you mock them will be trickier of course. I haven’t found a better method than manually writing my mocks yet (mimicking the asynch interface), but I’m definitely looking.

  6. @dan: That can definitely work for certain scenarios, but the problem is that I generally want to load a completely different test fake for each test case (and I have a multiple test cases in one file).

Leave a reply

required

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>