Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active September 30, 2021 15:43
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save domenic/5753428 to your computer and use it in GitHub Desktop.
Save domenic/5753428 to your computer and use it in GitHub Desktop.
How DI container config should work (in JS)
"use strict";
// Domenic needs a Tweeter
function Domenic(tweeter) {
this.tweeter = tweeter;
}
Domenic.inject = ["tweeter"];
Domenic.prototype.doSomethingCool = function () {
return this.tweeter.tweet("Did something cool!");
};
module.exports = Domenic;
"use strict";
// Merick also needs a tweeter
function Merrick(tweeter) {
this.tweeter = tweeter;
}
Merrick.inject = ["tweeter"];
Merrick.prototype.doSomethingAwesome = function () {
return this.tweeter.tweet("Did something awesome!");
};
module.exports = Merrick;
"use strict";
function App(domenic, merrick) {
this.domenic = domenic;
this.merrick = merrick;
}
App.inject = ["domenic", "merrick"];
App.prototype.run = function () {
this.domenic.doSomethingCool().done();
this.merrick.doSomethingAwesome().done();
};
module.exports = App;
"use strict";
var diContainer = require("di-container");
// Declaratively wire up dependencies. Note that while Domenic and Merrick both need the "tweeter"
// abstraction, we can choose *via configuration* to give them a different "tweeter" concretion.
// They are completely decoupled from this knowledge.
diContainer.config({
"app": require("./3-App"),
"domenic": {
constructor: require("./1-Domenic"),
inject: {
"tweeter": require("./LolSpeakTweeter-not-shown")
}
},
"merrick": {
constructor: require("./2-Merrick"),
inject: {
"tweeter": require("./LeetSpeekTweeter-not-shown")
}
}
});
// Construct the entire object graph, using above declarative config.
// This is the *only* time you should ever use `diContainer.get`.
diContainer.get("app").run();
// Your framework might use `diContainer.get` itself, e.g. for convention-based lookups.
// But you never should.
// Should Tweet:
// - "LOL I HAZ DID SOMETHING COOL LOL!"
// - "1 d1d s0m3th1ng 4w3s0m3!"
"use strict";
// You can also of course wire up the graph manually.
// That's still doing dependency injection.
var Domenic = require("./1-Domenic");
var Merrick = require("./2-Merrick");
var App = require("./3-App");
var LolSpeakTweeter = require("./LolSpeakTweeter-not-shown");
var LeetSpeekTweeter = require("./LeetSpeekTweeter-not-shown");
var app = new App(
new Domenic(new LolSpeakTweeter()),
new Merrick(new LeetSpeakTweeter())
);
app.run();
@royriojas
Copy link

I really believe that if you're using CommonJS modules already (with Browserify for example) you don't really need a DI container.

In order to prove it, I have rewritten the Coffee Maker example found in this video https://www.youtube.com/watch?v=_OGGsf1ZXMs#t=121

https://github.com/royriojas/coffe-maker

Basically you can just use the injectr approach of provide a second parameter to the require function.
This second parameter is the list of modules to be mocked when required inside the test. This means require is tampered in your testing environment.

So using the same file as the one you provided like:

var db = require('db');

function Foo(){
  this.client = db.createClient();
}

module.exports = Foo;

In testing env

var mockDB = {};
var Foo = require('./foo', { 
   'db': {
     createClient: function () { 
       return mockDB; // mocked client object; 
     }
  } 
})
var foo = new Foo();
expect(foo.client).to.equal(mockDB);

And that's it, now you can write your tests with ease.

I know for sure that DI can do way more things that just replace a mock during testing, but I would say that the vast majority of javascript projects just need to reuse the code during testing. So this will work.

Using this approach I was also able to switch implementations on runtime, that is a bit more complicated and involves playing with browserify to require/export modules, to make them available outside the bundle, but it is totally possible. So far I have not found a single feature provided by a DI container to make me thing we need one in Javascript.

I have provided a demo of how to do this with Karma and publish the code for be consumed in a browser.

I know also that ES6 modules are coming, I would rather prefer CommonJS format, but let's see what happens. It is good to have more options...

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