Dependency Injection
When I first started writing Android applications way back in the day I kept running across "Dependency Injection" and libraries to do whatever that was.
It was all the rage, and I had no idea what it was or what it was for.
In hindsight with the benefit of experience, it is really dead simple and very useful.
In a nutshell
class MyService {
constructor() {
this.adder = new Adder();
}
addOne(n) {
return this.adder.addOne(n);
}
}
vs
class MyService {
constructor(adder) {
this.adder = adder;
}
addOne(n) {
return this.adder.addOne(n);
}
}
That's it. It really is that simple.
I don't know why this was made to be so complicated or why no one could explain it in a simple fashion, but there you go.
By passing your dependencies to the constructor you have "injected" them into the class.

Straight into my veins!
Why bother?
There's two main reasons I find this to be very useful:
- Easier to test - If you're writing unit tests you'll want to mock your dependencies, this is much easier to do if you are injecting dependencies.
- Looser coupling - It's easier to swap components in and out
Easier testing?
If you haven't written a lot of unit tests before, imagine you want to write a test for the non-dependency-injected class:
import Adder from ../utils/adder.js
class MyService {
constructor() {
this.adder = new adder();
}
addOne(n) {
return this.adder.addOne(n);
}
}
How do you test this class in isolation? You can't really since you are depending on the imported Logger
. Sure you can mock it with the help of testing libraries, but it is not immediately clear how to do this.
Imagine now you want to test the dependency-injected version:
class MyService {
constructor(adder) {
this.adder = adder;
}
addOne(n) {
this.adder.addOne(n);
}
}
Ah hah! It should be clear now, you can test this service all by itself. It is immediately clear how to mock the Logger and write a test:
const myAdder = {
addOne: (n) => {
return n + 1;
},
};
const myService = new MyService(myAdder);
const testVal = 1;
const result = myService.addOne(testVal);
if (result !== testVal + 1) {
throw new Error("test failed");
}
That's great! We've written a unit test for MyService
without the help of any testing libraries. We've successfully mocked the dependency and all is well.
Decoupling?
It should also now be clear how easily we can swap out dependencies
const myFunkyAdder = {
addOne: (n) => {
let carry = 1;
while (carry) {
const temp = n ^ carry;
carry = (n & carry) << 1;
n = temp;
}
return n;
},
};
const myService = new MyService(myFunkyAdder);
const testVal = 1;
const result = myService.addOne(testVal);
if (result !== testVal + 1) {
throw new Error("test failed");
}
Certainly you can do all of that without injecting the adder, but it is definitely more of a pain.
I hope this also lifts some of the fog around unit testing too. There's no magic here and no libraries required, you can write tests all on your own. Testing libraries undoubtedly make this process easier, but we can be less bad at our jobs by understanding how to write tests on our own first.
Tangentially this should be a recurring theme in your journey as a developer, endeavour to understand how to do things on your own before immediately reaching for a library. It's a good idea to use battle tested software others have written, but only if you understand why it was written in the first place and what problem it is trying to solve.
Back to dependency injection, I hope the usefulness of this pattern is clear, we can write better code and build better things by following useful patterns.
In short, we can be a bad developers by writing code that just gets the job done. We can be less bad developers by writing code that gets the job done in well thought out ways.