I’ve been working with a large legacy codebase that doesn’t have great test coverage (which I guess is redundant since Michael Feathers defines legacy code as code without tests). As I fix bugs or add features, I also try to add unit tests along the way. But this can get pretty tricky pretty fast and one of the main roadblocks is a class’s dependency graph.
When a class calls out to another class to get data or perform an action, we say it is dependent on the other class. Here’s a quick example:
Here we’ve got a Musician class that can do one thing: sing a favorite song! Now let’s write a test for that.
We quickly run into a problem: there’s nothing to assert! How do we know if the musician is singing?
The real issue is that the Musician depends on the Microphone but our test has no way of accessing the Microphone. We can’t check if the Musician is using it correctly (or at all). Another major issue is that we don’t know what the Microphone is doing behind the scenes. For all we know it could be starting up a network connection or fetching data from a database. Our dependencies could have dependencies! This is getting awfully complicated.
Enter Dependency Injection
We need a way to separate out the dependencies in our Musician class. Let’s start by pulling the Microphone out into an instance variable.
Ok, that’s pretty straightforward. Now let’s write a test that makes use of this
mic instance variable. Let’s inject the Microphone dependency.
Hold on one second! This just took an interesting turn of events. Is that a new class declared inside of a function?! Coming from an Objective-C world, this blew my mind the first time I saw it. Dear iOS developers: welcome to the 21st century.
But back to the matter at hand. The new class,
MockMicophone is a subclass of
Microphone that remembers the last song it recorded. By injecting this new MockMicrophone into our Musician class we now have a way to verify behavior.
In a nutshell, this is what Dependency Injection is. It’s just a way to make explicit the dependencies your class has, and allow other objects to override those dependencies.
DI: Four Ways
There are four ways (that I know of) to accomplish dependency injection. Let’s run through them quickly with some examples:
The first and second methods are pretty self-explanatory. Many people like these methods because they make the dependencies explicit. When you go to use the Musician class, it is quite obvious what dependencies that class has.
We already explored the third method in this article. This is my preferred method when working with legacy code because it allows you to quickly separate dependencies and write tests without rewriting much of your production code. In the example we saw here, none of my production code that talks to the Musician class needs to change. All I had to do is extract an instance variable and voilà! I could start testing.
The fourth is pretty interesting and powerful but I’m not sure I’d recommend it. It removes the dependency on Microphone but it adds a dependency on new singleton called AppContext. What makes this powerful is that your test suite and your production code could have different AppContexts, allowing you to make sweeping changes quickly.
So there you have it! I once heard dependency injection called “a ten-dollar word for a ten-cent concept”. It’s a simple yet powerful tool to break up dependencies in your code and increase testability.
I started writing this blog post a couple weeks ago, coincidentally while listening to Prince. I was really saddened to hear about his passing a few days ago. I decided to keep the code samples as-is as a tribute to His Royal Badness.