Skip to content

Instantly share code, notes, and snippets.

@Raynos

Raynos/x.md Secret

Created August 6, 2012 00:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raynos/57a91462277331fdf5e9 to your computer and use it in GitHub Desktop.
Save Raynos/57a91462277331fdf5e9 to your computer and use it in GitHub Desktop.

Manual Dependency Injection in JavaScript

Dependency Injection is a pattern used to write modular, loosely coupled components. Specifically you inject specific dependencies into a module at run-time rather then loading them in a hard coupled fashion at compile time.

Before we delve into ways to do dependency injection let's take a look at a simple example that could benefit from DI (dependency injection)

var mongo = require("mongo-col")("DI example")

var store = module.exports = {
    set: set
    , get: get
}

function get(key, callback) {
    mongo.findOne({
        key: key
    }, function (err, result) {
        callback(err, result && result.value)
    })
}

function set(key, value, callback) {
    mongo.update({
        key: key
    }, {
        $set: {
            value: value
        }
    }, {
        upsert: true
        , safe: true
    }, callback)
}

Above is an example implementation of a little store library that uses mongoDB for persistance of key value pairs. It uses mongo-col to reduce mongoDB boilerplate code.

To illustrate why DI is useful let's try to test the above code.

// uses real database! naughty
var store = require("./index")
    , assert = require("assert")

store.set("foo", "bar", function (err) {
    store.get("foo", function (err, value) {
        assert.equal(value, "bar")
        console.log("DONE")
        // How do we close the database?
    })
})

We can identify a few issues with the above code

  • The database is used directly in our tests, this makes them slow and can also corrupt database state
  • We have no way to close the database connection in this test because it's an internal dependency we have no access to.
  • We also have no way to set the database up in an initial state.

Now ideally when we write a unit test we test the unit of code rather then the database. DI allows us to inject the database dependency of the store into module in such a way that we can inject a fake database when testing.

Constructor Injection

module.exports = storeConstructor

function storeConstructor(mongo) {
    return {
        set: set
        , get: get
    }

    function get(key, callback) {
        mongo.findOne({
            key: key
        }, function (err, result) {
            callback(err, result && result.value)
        })
    }

    function set() {
        ...
    }
}

Dependency injection using constructors is simple. Rather then exporting the module you export a function that takes the dependencies as arguments. Then when you come to test or use the module you make sure to call the constructor with the correct dependencies

var mongo = require("../mocks/mongo")
    , store = require("./store")(mongo)
    , assert = require("assert")

store.set("foo", "bar", function (err) {
    store.get("foo", function (err, value) {
        assert.equal(value, "bar")
        console.log("DONE")
    })
})

Then in our tests we get a mock version of the mongodb instance and we pass it directly into the store constructor.

For a more complete example see DI-example

This achieves our goal of wanting to write unit tests that do not interact with the database. There are other ways to do DI which have their own trade offs. We will look at those in a future article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment