Dependency injection — How it helps testingAditya SreekumarBlockedUnblockFollowFollowingApr 22IntroductionDependency Injection is a paradigm in which the object does not worry about initializing its dependencies, but instead expects to receive it from somewhere else — thus facilitating modular code.
A more theoretical explanation of this concept can be found in another article.
The aim in this article is to walk through an example of where Dependency Injection is useful and see how it facilitates easier unit testing.
ScenarioFor the purpose of this article, we consider the example of a home security system which needs to have the ability to ring a doorbell — ringDoorbell() — as well as keep track of how many times the doorbell has been rung — getNumberRings() .
Basic OO approachIf we start off with a trivial object oriented approach to this problem, we will create an object to encapsulate a door bell and use that in the SecuritySystem class.
Here, the SecuritySystem class creates an object of ButtonDoorBell.
internally and uses that for the APIs related to ringing a door bell.
The design issue here is that if we want to replace the button based doorbell with a video doorbell, we would have to now modify the class definition.
As you can probably imagine, the overhead with doing this every time we support a new type of doorbell would be cumbersome.
Dependency InjectionA better approach to the previous design problem is abstracting out the doorbell as an interface, and Dependency Injecting the concrete doorbell class.
To do this, we introduce a DoorBellIntf which specifies the APIs of a generic door bell.
The ButtonDoorBell class will implement that interface to give us a definition of the required APIs.
The SecuritySystem will use that interface and take in a concrete object in its constructor.
Extending the programIf we wish to add a new type of doorbell now, we just need to dependency inject it at run-time with no changes necessary in SecuritySystem .
Better Unit TestingThe above code highlights the advantage of dependency injection, but why do we assert that doing it makes testing SecuritySystem easier?The answer is quite simple — we can easily test the security system class for the ringBell API by creating a test version of the DoorBellIntf class.
We also add some helpers which allow us to manipulate the return value of ring API.
We now use this TestDoorBell class to write a test which creates a test doorbell, rings the doorbell and check the return value and ensures it is false.
Now, we want to test what happens when the DoorBell returns true.
This is simple as we already made a custom test helper to assist us:This ability to manipulate the dependent objects and test only the APIs we are concerned with is very important for unit testing and has been made much easier thanks to dependency injection.
It would not have been possible had we proceeded with our initial approach.
For those familiar with other testing frameworks (GoogleTest, Catch2, etc), dependency injection can be used to inject Mocks, Fakes and other test doubles.
Word of CautionDependency injection is not without its downsides.
If overdone, you can create classes which have every single API satisfied by a dependency injected object.
Managing this becomes difficult and unwieldy.
It also increases the complexity of code as it introduces a further level of indirection which might not be necessary.
If used properly however, dependency injection is a great way to create modular and testable code.
ResourcesGitHub code with examples which compile based on this article — https://github.
com/Aditya90/DependencyInjectionExample.. More details