Chances are you have used dependency injection without even knowing it! It sounds quite complicated, but in reality it is quite simple. It is a design pattern that causes a greater degree of decoupling and makes testing easier. It also allows for greater flexibility in your system by externalizing dependencies.
It allows the creation of objects on which a variable/object depends on someone else’s problem. Instead of constructing variables/objects within the system, you externalize them such that they are created somewhere and are passed in as arguments. In other words, externalizing things the system uses instead of internalizing them. A lot of big words, but let’s see how simple this really is.
In this example, we are going to be using javascript to create a function that determines the browser name and version from the property navigator.userAgent
which also happens to be a read-only property. I would not get caught up in what the function actually does, but more-so on how the function takes in navigator.userAgent
.
Without dependency injection:
function getBrowser() {
var ua = navigator.userAgent;
var tem,
M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if(/trident/i.test(M[1])){
tem= /\brv[ :]+(\d+)/g.exec(ua) || [];
return 'IE '+(tem[1] || '');
}
if(M[1] === 'Chrome'){
tem= ua.match(/\bOPR\/(\d+)/);
if(tem!= null) {
return 'Opera '+tem[1];
}
}
M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
if((tem= ua.match(/version\/(\d+)/i))!= null) {
M.splice(1, 1, tem[1]);
}
return M.join(' ');
}
We can then use the function as followed:
var output = getBrowser();
How would we go about testing this function? The way we architected this code, this function encapsulates navigator.userAgent
. We not only have to mock out the function but intercept the variable ua
by stubbing out navigator.userAgent
.
We could intercept ua
by stubbing out navigator.userAgent
by using this code I found from http://stackoverflow.com/questions/1307013/mocking-a-useragent-in-javascript
navigator.__defineGetter__('userAgent', function(){
return 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0' // customized user agent
});
expect(getBrowser()).toEqual('Firefox 36');
Remember, navigator.userAgent
is a read-only property. We did something janky here to have navigator.userAgent
take on different values for testing which shouldn’t even be done in the first place given that it is a read-only property. This code also sucks in that it doesn’t work in all browser environment as the StackOverflow comments show.
We see that the function is dependent on navigator.userAgent
. What happens if we externalize this dependency by injecting it into our function.
With dependency injection:
function getBrowser(userAgent) {
var ua = userAgent;
var tem,
M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if(/trident/i.test(M[1])){
tem= /\brv[ :]+(\d+)/g.exec(ua) || [];
return 'IE '+(tem[1] || '');
}
if(M[1] === 'Chrome'){
tem= ua.match(/\bOPR\/(\d+)/);
if(tem!= null) {
return 'Opera '+tem[1];
}
}
M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?'];
if((tem= ua.match(/version\/(\d+)/i))!= null) {
M.splice(1, 1, tem[1]);
}
return M.join(' ');
}
We can then use the function as followed:
var output = getBrowser(navigator.userAgent);
Notice how the responsibility of generating the userAgent is externalized instead of internalized? We didn’t change much at all too which is great news! Best of all, look how easy it is to test now.
var agent = 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0';
expect(getBrowser(agent)).toEqual('Firefox 36');
Notice how easy it is to stub out navigator.userAgent
as this becomes an external dependency instead of an internal dependency. You probably will only pass in navigator.userAgent
, but let’s assume you had a scenario where you would like the variable ua
to take on something other than navigator.userAgent
. Notice how much more flexible the function becomes as you are allowed to explicitly pass in any value to the function.
==Dependency injection means giving an object its instance variables. In this example, we gave the function its instant variable ua.==
That is pretty much all there is to it. This example demonstrates one use case for dependency injection, but there are tons more. You could probably look through your code now and find a couple places where you can benefit from dependency injection in creating both a higher degree of flexibility and a happier testing environment.
I’m @steventsooo on Twitter. I would love to hear what you think!