Dependency Injection is more than just avoiding "new"

Friday, November 20, 2020

Introduction


This post is the second part of our new series "Design for Testability". In the first part we looked at why architectural patterns provide better testability. Now I want to take a deeper look into software design patterns and how they can influence better testability.

One well-known software design pattern is dependency injection. It is based on the Inversion of Control Principle, also known as the Hollywood Principle: "Don't call us, we'll call you". But what does this exactly mean and how is this linked to dependency injection? And how can we in the end implement dependency injection principles for better testability? These questions I want to answer today.

Inversion of Control or Dependency Injection?

Of course, you'll find a lot of information and articles out there about both terms. I want to give a basic explanation because they get often mixed up.

Basically, both terms, Inversion of Control and Dependency Injection, mean the same. Or as Martin Fowler puts it:

As a result I think we need a more specific name for this pattern. Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various IoC advocates we settled on the name Dependency Injection.

You can see the Inversion of Control as the underlying principle of the Dependency Injection pattern. But what is actually inverted here? Let's take a basic example:
{% c-block language="cs" %}
public class Car
{
      
  private Foo _foo;      
  
  public Car()    
  {         
    _foo = new Foo();    
  }
}
{% c-block-end %}

Here we simply create a new instance of the field _foo inside the constructor of the class Car. What happens here is that we control when and how the initialization happens. The control is completely in our hands. The class Car has complete control over the initialization process and it is tightly coupled to the newly created foo object. This is one major obstacle to testability!

If you want to create a Unit Test for the functionality of the Car class then you always initialize the Foo class as well. You are never able to create a Mock of this Foo class.

But if you invert the control and give it to some kind of "Framework", you get rid of this tight coupling. The "Framework" would control the initialization of our _foo dependency instead of the Car class itself. This is called Inversion of Control.

How do we implement this Inversion of Control principle? This is where the Dependency Injection pattern comes in.

How to implement DI correctly?

Injecting a dependency into a class can be realized in three different ways:

  • Method injection
  • Property injection
  • Constructor injection

Each injection type has its advantages and drawbacks. In most cases, it depends on your overall software design and software architecture which defines what dependency injection type you want to use.

In most business applications, you use some kind of InversionOfControl (IoC) Container for injecting your dependencies into your class. The container handles the registration of your dependencies and wires everything together. If you inject a dependency into your class the container provides you a new instance for this specific dependency.

For the following examples, I'm using the C# programming language. But it is more or less the same for every object-oriented programming language.

Method Injection

Here the dependency is simply injected into the method header as a parameter.

GIST: https://gist.github.com/adrinamin/8f0a6ddf940adc6a35d78467bcf6c2a4.js

The HospitalService.AddPatient method contains an extra parameter for the IMessageService dependency. Here the client can inject the dependency instance. If your class only needs a dependency inside a specific method, Method Injection is the preferred choice.

Property Injection

In C#, we have the property syntax which is basically syntactic sugar for writing getter and setter methods. Thus we reduce a lot of boilerplate. In Java, for example, you have to write these methods but there are libraries, like Project Lombok, which automatically create getter and setter for you.

No matter which language you use, Property Language (also called Setter Injection) can be implemented anyway. In our C# this looks as follows:

GIST: https://gist.github.com/adrinamin/05c5fcb1f3c8dbdd20ab26304321f9f3.js

The HospitalService.MessageService is initialized inside the Main method. Therefore, the backing field of the MessageService property is initialized as well. This is backing field is used for further processing.

With property injection, you can use the dependency inside your entire class and you are not limited to a specific method. Keep in mind that the initialization of your class and of your property are independent of each other. You really need to verify that you initialize your dependency before it is used for any operation inside your class. Otherwise, this will result in exceptions.

Constructor Injection

The best-known injection type and most common way to initialize your dependencies are via Constructor Injection.

GIST: https://gist.github.com/adrinamin/88c24cc076170e4b8ae333e76ae2bcb8.js

The MessageService dependency is injected as a constructor parameter into the HospitalServices. Like Property Injection, your dependency is available for the entire class. But this time it is also initialized at the same time when the HospitalService is created. This is the reason why most IoC Container and Framework use Constructor Injection. All components are easily wired together and you know when you create a new instance of HospitalService, all its dependencies are also initialized exactly the way you want.

What does this have to do with testability?

Well, as I said at the beginning, with Dependency Injection we provide loose coupling. Your class only contains interfaces of its dependencies but the concrete functionality is encapsulated and cannot be changed. If you use an IoC Container, the registration of all your components is handled by it. Everything is wired together by the container and it injects dependencies into the corresponding classes. You can substitute those dependencies for every given time. If you write Unit Tests, for example, you can inject a MessageServiceMock into the constructor of your HospitalService. So you can easily only test the functionality or feature you want.

Conclusion

Even though Constructor Injection is most used and usually sufficient, there are sometimes situations where you don't need or don't want to use it. Therefore it might be useful to consider other types of Dependency Injection without losing the advantage of loose coupling and good testability.

To summarize everything, here are the key takeaways:

  • Inversion of Control is the underlying principle of the Dependency Injection pattern.
  • In most cases, Constructor Injection is sufficient.
  • Keep in mind, when using Property Injection, you have an extra step to initialize for initializing the property of a class.
  • Method Injection can be useful if you want to limit the usage of a dependency on just one method of a class.
  • With Dependency Injection, we can easily swap dependencies and use mocks or stubs.

Let me know what you think and stay tuned for part 2 of this series. If you can't wait, you can also check out my video on "Design for Testability".

Let us know what you
think about this article