unit-testing Dependency Injection


One approach that can be taken to writing software is to create dependencies as they are needed. This is quite an intuitive way to write a program and is the way that most people will tend to be taught, partly because it is easy to follow. One of the issues with this approach is that it can be hard to test. Consider a method that does some processing based on the current date. The method might contain some code like the following:

if (DateTime.Now.Date > processDate)
    // Do some processing

The code has a direct dependency on the current date. This method can be hard to test because the current date cannot be easily manipulated. One approach to making the code more testable is to remove the direct reference to the current date and instead supply (or inject) the current date to the method that does the processing. This dependency injection can make it much easier to test aspects of code by using test doubles to simplify the setup step of the unit test.

IOC systems

Another aspect to consider is the lifetime of dependencies; in the case where the class itself creates its own dependencies (also known as invariants), it is then responsible for disposing of them. Dependency Injection inverts this (and this is why we often refer to an injection library as an "Inversion of Control" system) and means that instead of the class being responsible for creating, managing and cleaning up its dependencies, an external agent (in this case, the IoC system) does it instead.

This makes it much simpler to have dependencies that are shared amongst instances of the same class; for example consider a service that fetches data from an HTTP endpoint for a class to consume. Since this service is stateless (i.e. it doesn't have any internal state) therefore we really only need a single instance of this service throughout our application. Whilst it is possible (for example, by using a static class) to do this manually, it's much simpler to create the class and tell the IoC system that it's to be created as a Singleton, whereby only one instance of the class every exists.

Another example would be database contexts used in a web application, whereby a new Context is required per request (or thread) and not per instance of a controller; this allows the context to be injected in every layer executed by that thread, without having to be manually passed around.

This frees the consuming classes from having to manage the dependencies.