Dependency Injection with Node.js

Dependency Injection with Node.jsRecently, I went over Dependency Injection to help you understand a simple way to decouple your code a little bit and help your testing out. Sometimes, though, in Node.js a module will depend on a system API provided by Node, which can make it pretty difficult to make sure that private dependency is being used properly. Normal dependency injection doesn't work in this situation, but don't give up hope just yet.

require Causes Issues

Node.js made it really easy to import dependencies via require. It works very nicely and is simpler than AMD module loaders such as RequireJS.The problem comes into play when we want to mock those dependencies. If module loading is controlled via Node.js, how do we take over this responsibility to allow mock objects to be used instead during testing? We can use Node's vm module and load the modules into a new context, via vm.runInNewContext, where we can control the way require gives back modules.

The Solution

Thanks to this article, a pretty decent and thorough solution can be presented to you right now. If you like this solution, then please give Vojta Jina over at How To Node the thanks and credit. Below is the code:

var vm = require('vm');
var fs = require('fs');
var path = require('path');

/**
 * Helper for unit testing:
 * - load module with mocked dependencies
 * - allow accessing private state of the module
 *
 * @param {string} filePath Absolute path to module (file to load)
 * @param {Object=} mocks Hash of mocked dependencies
 */
exports.loadModule = function(filePath, mocks) {
    mocks = mocks || {};

    // this is necessary to allow relative path modules within loaded file
    // i.e. requiring ./some inside file /a/b.js needs to be resolved to /a/some
    var resolveModule = function(module) {
        if (module.charAt(0) !== '.') return module;
        return path.resolve(path.dirname(filePath), module);
    };

    var exports = {};
    var context = {
        require: function(name) {
            return mocks[name] || require(resolveModule(name));
        },
        console: console,
        exports: exports,
        module: {
            exports: exports
        }
    };

    vm.runInNewContext(fs.readFileSync(filePath), context);
    return context;
};

You can also download the code snippet directly from here. While it might not be the largest chunk of code ever posted into an article, it could still use some explanation. When we are testing, we will load this module into the test, and then use the loadModule function – instead of require – to load in the module we will be testing.

The first argument, filePath, specifies where we'll find the module that we'll be testing. The second argument, mocks, contains an object whose property names will match the names of modules that the module we are testing will try to require. The value assigned to those properties are the mock objects that you are using to replace the modules that would normally be required.

Basically, all it does is use vm to load and run the module using a different "context". In other words, we recreate what the globals are (such as require and exports) so that we can control them. The thing to notice here is the new require function that we make available. All it does is check to see if we have a mock dependency for the specified name, and if we don't, we just delegate to the normal require function.

Example Using the Module Loader

If you're still a bit confused, you can take a look at the example code below and seeing it used in context might help you figure things out a bit. First, we'll just create a simple module.

var fs = require('fs');

module.exports = {
// Do something with `fs`
}

Just imagine it is something cool, ok? Anyway, now we want to test that module, but we want to mock fs to see how it's being used internally.

// Jasmine's syntax http://pivotal.github.com/jasmine/
describe("someModule", function() {
  var loadModule = require("module-loader").loadModule;
  var module, fsMock;

  beforeEach(function() {
    fsMock = {
      // a mock for `fs`
    };

    // load the module with mock fs instead of real fs
    module = loadModule("./web-server.js", {fs: fsMock});
  });

  it("should work", function() {
    // a test that utilizes the fact that we can now control `fs`
  });
});

The main thing to pay attention to here is lines 7 through 12, where we create a mock object for fs and use our new loadModule function to tie the mock object in as the object being used in our above worthless little module (I mean awesome! Remember, it's awesome, right?).

Conclusion

In my mind, this just fortifies the greatness of Node.js. It allows you to change the context in which it runs! This is a really interesting way to emulate dependency injection, and I'm sure it can be useful for far more. Anyway, keep testing, keep using good practices, and as always, God bless and happy coding.

About the Author

Author: Joe Zim

Joe Zim

Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.


  • foo

    test