This is part two of a three part series on dependency injection mistakes. All the articles in the series are listed below:
- The Static or Singleton Container.
- Configuring the IoC Container in Unit Test Projects.
- Using XML over Fluent Configuration.
What is unit testing?
Unit testing by definition is concerned with testing individual units or components. If a particular class is under test, we can do two types of testing. State based testing is concerned with inputs and outputs. Any dependencies should be stubbed either manually or using a mocking framework such as RhinoMocks or Moq. Interaction based testing on the other hand is concerned with determining how the class under test uses its dependencies - how it interacts with other classes. This time dependencies are mocked so we can determine how many times certain methods are called and what parameters are passed to these methods. Typically your unit test suite will consist of both types of test.
To put it another way, if component A calls component B then from a unit testing perspective, we cannot let component A call the actual implementation of component B. Instead, component B must be mocked. We use a fake instead of the real component B so that 1) our tests do not rely on code in any other class, 2) component B returns the same data every time and 3) we can intercept calls to component B so we can check how and when it is being called.
How does dependency injection facilitate unit testing?
If component A has a constructor that takes in the interface that component B implements, then when testing, it is very easy to pass in a different test implementation. We can create this test implementation manually or using a mocking framework. Either way, in our unit tests we just manually instantiate the class under test (Component A) and pass the test implementation (the mock) in the constructor.
// arrange IComponentB componentB = new MockComponentB(); // manual approach var componentB = MockRepository.GenerateMock<IComponentB>(); // Rhino Mocks approach var componentA = new ComponentA(componentB); // act var result = componentA.MakeSomeCall(42); // assert
With dependency injection, unit testing is very straightforward. Tests are very readable, no special tools are necessary and absolultely no configuration is required. We talked in part one of this series that dependency injection is not dependent on the use of an IoC container. DI is a design consideration and an IoC container is simply a tool to aid resolution of your dependency graph. We do not have to use an IoC container in our unit test code above and nor should we need to.
How does the use of an IoC container facilitate unit testing?
As we have seen above, if you are doing Dependency Injection then typically, you do not need an IoC container in your unit tests. There are exceptions such as when testing framework or infrastructure code, but on the whole, IoC containers are not necessary when unit testing code that uses DI.
If you are mis-using your container as a service locator and are not injecting your dependencies, then as we have already noted, your code will not even execute without the container. Therefore, when unit testing, you will also require the container. To test thoroughly, you will need multiple different container configurations to resolve mocked and stubbed dependencies, but anyone going down this approach will probably not even bother - it involves so much effort that the majority of 'unit tests' will just mock any database or web service call and let multiple components run within a single test.
The simple fact is that if you are doing dependency injection properly, then your code will be unit testable. This is a pleasant by-product of writing loosely coupled code. The important thing to understand is that in most application scenarios there is no need for IoC containers in unit test projects. Typically, it is only when testing framework code that a reference to the container might be required, but that is outside the scope of this article. In typical, application-focused unit tests, dependencies are satisfied manually as constructor arguments when instantiating the class under test. There should never be a need to resolve a huge dependency graph.
Of course, if you are using an IoC container incorrectly and are not doing dependency injection, then you will find unit testing very difficult and your code may be untestable without referencing and configuring the container from the unit test project. Instead of spending hours trying to hack a solution together, why not address the real issue - remove your static container, implement dependency injection properly and you will never look back.