Inversion of Control is NOT about testability

Many times, I’ve heard that the best thing about IoC is that it enables testability.  When dependencies are injected into a class, it’s easy to test that class.  Testability is a side-effect of good design.  The lack of testability signifies a bad design, but the enable to test a class does not, by itself, signify a good design.  The causality is one-way.

Let’s take a look at an example:

 

public class MissileLauncher{
    private INavigator _navigator;
    public MissileLauncher(){
        _navigator = new MissileNavigator();
    }
    public void Launch(){
        //get direction guidance from _navigator and launch a missile
    }
}

 

This can be a good design or bad design depending on your needs.  We are coupling to an interface, INavigator, but we are also coupling to a specific implementation class, MissileNavigator.

If MissileLauncher can’t do its job without MissileNavigator, then this coupling makes sense, and the class should be tested together with MissileNavigator.  Other examples are operations on domain objects:  The logic makes no sense without the Customer object, specifically, so the operation should be tested with the real Customer, and not a mock object.

If, however, MissileLauncher is a concept orthogonal to MissileNavigator, then INavigator adequately illustrates the necessary interaction.  In this case, coupling to MissileNavigator is detrimental to our design if the MissileLauncher only depends on the operation exposed by INavigator.

We would change this class to the following:

public class MissileLauncher{
    private INavigator _navigator;
    public MissileLauncher(INavigator navigator){
        _navigator = navigator;
    }
    public void Launch(){
        //get direction guidance from _navigator and launch a missile
    }
}

Here, we have inverted the control over locating or creating the dependency.  Now MissileLauncher does not care about the actual class, just that it receives an instance of the interface.  With the interface it can do its job.  This is a better design if you have determined MissileLauncher’s behavior is orthogonal to the actual MissleNavigator class.  Again, you have to make many hard decisions about design.  Do you couple. . . or not. 

In order to do anything useful in software, you must couple.  You must couple to something. 

The coupling determines how you will test.  If you couple to a concrete class, you will test the code together with that class.  If you only couple to an interface, you can test the class with only the interface.

Testability is a by-product of inversion of control, and design is the driving factor to invert the control or retain it.  Don’t invert all control just so each class can be tested independently of all other classes.  You must decide where coupling is appropriate and where it is not.