TDD and Isolating Dependencies
Michael Puleio and I recently gave a few talks on Test Driven Development (TDD). Several developers in our audience told us about how difficult their unit testing was due to instability of dependent services. They did not see how TDD could help them. Since their code leveraged potentially unstable and rapidly changing services, they had tremendous difficulty unit testing their code.
This is a very common issue. The basic problem is that our code will often have external dependencies. Whether the dependency lies in a remote web service, locally referenced assembly, or even in a class in the same project, I contend that these dependencies need to be similarly isolated. I'd imagine their code to look something like the following...
public class OrderController
{
ICustomerRepository customerRepository = null;
public OrderController()
{
this.customerRepository = new VeryVolitileAndUnstableCustomerRepositoryService();
...
}
...
}
Since my OrderController wraps the VeryVolitileAndUnstableCustomerRepositoryService, every instance of my OrderController will be as unstable as its dependent service(s).
A common approach to remedy this situation is to clearly understand my code's dependencies and to have these dependencies pass in via constructor. This way we can pass in mock implementation of the dependency for unit testing and pass in a real implementation in the production code. You could even use a dependency injection container for resolution of dependencies, but that's another discussion.
public class OrderController
{
ICustomerRepository customerRepository = null;
public OrderController(ICustomerRepository customerRepository)
{
this.customerRepository = customerRepository;
...
}
...
}
My unit tests will typically have an instance of the class being tested and mocks for everything else.
[
TestMethod]
public void OrderControllerInitializesAppropriately()
{
MockCustomerRepository customerRepository = new MockCustomerRepository();
OrderController orderController = new OrderController(customerRepository);
Assert.IsNotNull(orderController);
Assert....
}
If OrderController newed up its own instance of VeryVolitileAndUnstableCustomerRepositoryService, and if the service were to not be available, most/all my unit tests would fail. Where do I start looking for the problem???
By isolating and testing my OrderController from any real (non mock) implementations, only modifications to OrderController or dependent interfaces (ICustomerRepository) will cause my unit tests to fail.
For a discussion of mock objects and stubs....