Simple Dependency Injection

Dependency injection is an important concept for anyone to understand before trying to get into test driven development. Recently I've noticed that lots of people are trying to get into agile practices of software development. They're writing tests, and plenty of them are writing integration tests instead of unit tests.

Integration Tests Versus Unit Tests

When testing there are two main types of tests which are created. The ones most people write are integration tests, and this I attribute to their not knowing about dependency injection. If your code contains too many dependencies you will not be able to write unit tests.

Unit tests are the tests you need the most of usually. Unit tests should be testing the logic of your code. The core of your application should be your domain objects, your business logic, and well as the name states it should be the core code required for your application run. Unit tests should be fast. Because well-tested code will have hundreds of unit tests for even very small projects, it is important that unit tests run extremely quickly. This means that unit tests should never ever access external resources. This means your code in unit tests should never access a database or even use configuration files. Another reason to not access external resources is that they would need to be kept in a known state at all times for the test to work correctly.

Integration tests are also vitally important to a healthy and tested application. These tests serve a different role though. These tests aren't so much testing the pieces of your code like the unit tests are. The integration tests are here to make sure everything works together including the external resources. Integration tests do exactly as their name states, they make sure that everythign integrates well together.

Don't spend your time testing every case with integration tests. That is the responsibility of the unit tests. Unit tests are there to try to test all the different logical cases that your code handles. The unit tests should be examining a small portion of the code at a time. The integration test is there to make sure that each piece of the code is able to interact with each part with which it is supposed to interact. This allows you to make sure everything is linked together correctly. This means you should have some simple data accessing tests, mapping tests to verify that the properties of your classes mesh with the columns in your database tables.

Testing With dependencies

Using some very common methods of software development people don't build software in a very testable way. This means that we need to make some adjustments to how a lot of people have learned to structure their applications. Most code is able to be integration tested. Why? Integration tests are easier to do because our code is too tightly coupled together to test the individual pieces we would test using unit tests. Most software has so many dependencies in it that even simple unit tests are impossible, because they'll become integration tests if we start touching a database, a file, a web service, or anything else external to our code. If you're not testing one single tiny piece of code, then you're integration testing. Integration testing makes sure the dependencies are all working. Unit tests are testing only a unit of the code at a time. The problem is that once you test an object and its dependency you're also testing the dependency. This is why we want unit tests; so we can control what we are testing.

We need to break these dependencies before we can write our unit tests correctly. Once we've broken these dependencies we should be able to test the code.

Programming Against Interfaces

As a general rule, developers should program against an interface. Be as generic as possible. By interface I mean that in the normal sense not the programming one. Interfaces and abstract classes and anything similar are all acceptable. The point is that you're not programming using concrete classes. When the code executes it will run against concrete implementations of the interface, but most of the code should just use the interface. This allows us to substitute in implementations which are fake and allow us to manipulate their results.

Injecting the Dependency

In a previous post I wrote about how to begin unit testing, but I didn't explain the dependency injection very well. In that post I created an ICalendar interface. I programmed against this interface. Then in my tests I used a FakeCalendar class which implemented the interface, and I manipulated the values returned by that class so that I could test what I wanted to test. I have also created a concrete implementation of the ICalendar interface, and I use it as the default implementation. I used the simple dependency injection I refer to in this article.

I create two constructors for the class with the dependency; one that takes the dependencies and one that is the default constructor. The one with the extra parameters will be used by the test methods so that I can pass in the fake implementations, and the default one will be used by my production code. This lets me use different implementations between test and production code without having to muck up the production code.

This is an example of what a class might look like after the dependency has been removed. Pay attention to the two constructors and the fact that I am writing code against an interface and not a concrete class.

public class TimeOfDay
{
    private ICalendar _calendar;
    public TimeOfDay() : this(new Calendar())
    {
    }
 
    public TimeOfDay(ICalendar calendar)
    {
        _calendar = calendar;
    }
 
    public bool IsMorning()
    {
        return (_calendar.GetCurrentTime().Hour < 10);
    }
 
    public bool IsEvening()
    {
        return (_calendar.GetCurrentTime().Hour >= 18);
    }
}

 

Update: It seems that I failed. Steve Smith mentioned in a comment below that I forgot to mention what this pattern is called. This is the strategy design pattern. It follows the principle of programming to an interface instead of to a concrete class. The point of the pattern is that you select the algorithm (in our case a class) which you will be using at runtime. Testing is just one use of the strategy pattern. It is also very useful in general purpose coding.

Comments