Inversion of Control (IoC) / Dependency Injection

When I first started building unit tests for my code, I would create a little user interface through which I could plug in various inputs and see the output. It was a highly manual process. But, it was also NOT unit testing. Rather, I was integration testing my objects and any objects referenced by my objects. Even when I began using tools like NUnit to do unit testing, I continued writing integration tests and calling them unit tests. It wasn’t until I learned about the “inversion of control” (or, IoC) design pattern that I began writing truly discreet tests.

Smarter people than I have written a bunch about IoC. So, rather than try to explain it, let’s look at an example. The following code uses a logger to write messages to an event log:

public class Calculator
{
    public int Add(params int[] args)
    {
        var logger = new EventViewerLogger("Application");
        logger.Log("Add");

        int sum = 0;
        try
        {
            foreach (int arg in args)
                sum += arg;
        }
        catch (OverflowException ex)
        {
            logger.Log("OverflowException: Add");
            throw ex;
        }

        return sum;
    }
}

Notice that the method directly instantiates the logger. Imagine trying to test this method without also testing the logger. It is simply not possible. Furthermore, this code violates both the Single Responsibility Principle and the Open/Closed Principle.

The Single Responsibility Principle states that a class/method must have only one responsibility. In this case, the appropriate responsibility is mathematical calculations. Instantiating objects counts as a second responsibility. Thus, the violation.

The Open/Closed Principle states that a class/method should be open for extension, but closed for modification. The way I’m using this term, the violation is in the fact that the Add method creates an instance of a concrete class, rather than referencing an interface. In doing so, it is impossible to change the logging behavior of this class/method without modifying the class itself.

Here’s how that same code would look after addressing these issues:

public class IocCalculator
{
    public IocCalculator(ILogger logger)
    {
        Logger = logger;
    }

    private ILogger Logger { get; set; }

    public int Add(params int[] args)
    {
        Logger.Log("Add");

        int sum = 0;
        try
        {
            foreach (int arg in args)
                sum += arg;

        }
        catch (OverflowException ex)
        {
            Logger.Log("OverflowException: Add");
            throw ex;
        }

        return sum;
    }
}

Now, the logger is instantiated outside the method. In fact, it is instantiated outside the class entirely and passed in via a constructor. (This is called Constructor Dependency Injection, which is a form of IoC.)

Furthermore, the class now references the ILogger interface rather than the EventViewerLogger concrete class. This allows for the client object to determine which logger should be used. In a production environment, that might be the EventViewerLogger, or it might be a DatabaseLogger. More interesting to this discussion, however, is the fact that now we can use a MockLogger, FakeLogger or StubLogger to test the calculation code without also testing the logger code.

So, what are mocks, fakes and stubs? They’re the subject of a future post. Stay tuned.

0 responses