How not to do dependency injection - configuring the IoC container in unit test projects
You will often hear people say that using IoC allows your code to be unit testable, and they are of course correct. If you are doing dependency injection correctly, you code will inherently be unit testable. The subtlety that needs to be understood is how DI aids unit testing. It is certainly not the use of an IOC container which should have absolutely no bearing on your unit testing code.
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:
- Our tests do not rely on code in any other class.
- Component B returns the same data every time.
- 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 absolutely 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.
* One legitimate reason why you might want to have an IoC container inside a unit test assembly even when using DI is when using the auto-mocking container. In short, this technique uses the power of the IoC container to automatically inject stubs into the class under test, cutting down on boiler-plate clutter in test methods and making the tests more maintainable. A popular library for this is AutoFixture. This concept is quite separate from the problem that we are talking about here and is perfectly valid.
Conclusion
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.
> The important thing to understand is that there is no need for IoC containers in unit test projects and in fact, an IoC container should NEVER be referenced by ANY unit test project.
This is not necessarily true. Absolute statements about a subjective topic such as this are minefields, especially when they are derived from generalizations. It's more helpful to say that you should avoid this pattern unless, with an understanding of the tradeoffs, you have a good reason to use it.
Ultimately, I believe you are making an argument against service location here and that its impact on testability is a second order effect of that.
There can be good reasons to use service location, particularly if you're writing framework code. For example, to receive a message off a queue and dispatch it to a handler, or how MVC implements action/controller dispatching with an optional static container.
@Shawn - Thanks for the feedback. You are completely right. I was really talking about typical application unit tests, but the article did not make this clear and the conclusions drawn went too far. I have updated the article to make it more general.