I am totally confused. There are many sources out there contradicting each other about the definitions of Dependency Injection and Inversion of Control. Here is the gist of my understanding without many additional detail, that in most cases made things more convoluted for me: Dependency Injection means that instead of my function conjuring the required dependencies, it is given the dependency as a parameter. Inversion of Control means that, for instance when you use a framework it is the framework that calls your userland code, and the control is inversed because in the 'default' case your code would be calling specific implementations in a library.
Now as I understand, somehow along the way, because my function that doesn't conjure up the dependencies anymore but gets it as an argument Inversion of Control magically happens when I use dependency injection like below.
So here is a silly example I wrote for myself to wrap my head around the idea:
getTime.js
function getTime(hour) {
return `${hour} UTC`
}
module.exports.getTime = getTime
welcome.js
function welcomeUser(name, hour) {
const getTime = require('./time').getTime
const time = getTime(`${hour} pm`)
return(`Hello ${name}, the time is ${time}!`)
}
const result = welcomeUser('Joe', '11:00')
console.log(result)
module.exports.welcomeUser = welcomeUser
welcome.test.js
const expect = require('chai').expect
const welcomeUser = require('./welcome').welcomeUser
describe('Welcome', () => {
it('Should welcome user', () => {
// But we would want to test how the welcomeUser calls the getTime function
expect(welcomeUser('Joe', '10:00')).to.equal('Hello Joe, the time is 10:00 pm UTC!')
})
})
The problem now is that the call of the getTime function is implemented in the welcome.js function, and it can not be intercepted by a test. What we would like to do is to test how the getTime function is called, and we can't to that this way. The other problem is that the getTime function is pretty much harcoded, so we can't mock it, and that could be useful because we only want to test the welcomUser function separately, as that is the use of a unit test (the getTime function could be simultaneously implemented, for instance).
So the main problem is that the code is tightly coupled, it's harder to test and it is just wreaking havoc all around the place. Now let's use dependency injection:
getTime.js
function getTime(hour) {
return `${hour} UTC`
}
module.exports.getTime = getTime
welcome.js
const getTime = require('./time').getTime
function welcomeUser(name, hour, dependency) {
const time = dependency(hour)
return(`Hello ${name}, the time is ${time}!`)
}
const result = welcomeUser('Joe', '10:00', getTime)
console.log(result)
module.exports.welcomeUser = welcomeUser
welcome.test.js
const expect = require('chai').expect
const welcomeUser = require('./welcome').welcomeUser
describe('welcomeUser', () => {
it('should call getTime with the right hour value', () => {
const fakeGetTime = function(hour) {
expect(hour).to.equal('10:00')
}
// 'Joe' as an argument isn't even neccessary, but it's nice to leave it there
welcomeUser('Joe', '10:00', fakeGetTime)
})
it('should log the current message to the user', () => {
// Let's stub the getTime function
const fakeGetTime = function(hour) {
return `${hour} pm UTC`
}
expect(welcomeUser('Joe', '10:00', fakeGetTime)).to.equal('Hello Joe, the time is 10:00 pm UTC!')
})
})
As I understood, what I did above was Dependency Injection. Multiple sources claim that Dependency Injection is not possible without Inversion of Control. But where does Inversion of Control come into the picture?
Also what about the regular JavaScript workflow, where you just import the dependencies globally and use them later in your functions, instead of require-ing them inside of the functions or giving it to them as parameters?
Check Martin Fowler's article on IoC and DI. https://martinfowler.com/articles/injection.html
IoC: Very generic word. This inversion can happen in many ways.
DI: Can be viewed as one branch of this generic word IoC.
So in your code when you specifically implements DI, one would say your code has this general idea of IoC in the flavor of DI. What really inversed is, the default way of looking for behavior (default way is writing it within the method, inversed way would be getting behavior injected from outside).