Refactoring to DI

Here is a fairly generic pattern for using refactoring to change an existing design with an embedded dependency into one that supports Dependency Injection (DI) for testing purposes.  In many ways, the code transformations I'll show below are similar to the ones in this article by Martin Fowler.

Note: although I am a fanatical TDDer, I've not used that approach when designing this refactoring.  The primary audience for this article probably don't practice TDD.

Example Code

Below is the code we'll be refactoring.  In short, it's a small class designed to calculate shipping charges on a product of some description.  The charge calculation process is largely based on the weight of the product, although additional charges are applied during certain peak shipping periods (i.e., Christmas).

Simplistic though it is, this example is sufficient to demonstrate the problems having embedded dependencies can have on writing an effective set of tests.

import java.math.BigDecimal;
import java.util.Calendar;

public class ProductShipperV1 {
    private static final float COST_PER_KILOGRAM = 1.25f;
    private static final float CHRISTMAS_LOADING = 2f;

    public BigDecimal calculateShippingCharges(Product product) {
        float weightInKilograms = product.getWeightInKilograms();
        if (weightInKilograms <= 0) {
            throw new IllegalArgumentException("Product has an invalid weight of " + weightInKilograms);    
        }

        BigDecimal charges = new BigDecimal(weightInKilograms * COST_PER_KILOGRAM);

        return charges.add(new BigDecimal(calculateAdditionalCharges()));
    }

    private float calculateAdditionalCharges() {
        Calendar calendar = Calendar.getInstance();
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);

        if (duringChristmasPeriod(month, day)) {
            return CHRISTMAS_LOADING;
        }

        return 0f;
    }

    private boolean duringChristmasPeriod(int month, int day) {
        return month == 11 && day > 23 && day < 31;
    }
}

Refactoring Steps

  1. Identify the dependency
  2. Move the responsibility for dependency creation out of the class being tested
  3. Inject the dependency into the class being tested (via constructor, parameter or setter)
  4. Extract interface out of class responsible for dependency creation
  5. Change type of DI to new interface
  6. Implement interface to create stub or mock
  7. Test using stub/mock when needed

Identify the dependency

This is the easiest part and I suspect you wouldn't be reading this article if you hadn't already passed this step.  My definition of a dependency is "an external entity referenced by your code which is (generally) outside your control and makes testing difficult".  Typical dependencies are:
In our case, the dependency is good old Father Time himself.  The reference to Calendar.getInstance(), although seemingly innocuous, links the code to the clock of the machine on which it is executing, and even though software developers can tend to be messianic on occasions, few of us have demonstrated any control over the eternal march of time.  This presents us with a problem testing this codebase: we cannot write a deterministic test of the Christmas shipping charges logic because we cannot control the time returned to us by the environment.  Actually, if we wanted to we could adjust the system clock before running the test and then reset it afterwards, but that's a sub-optimal solution compared to the one we'll arrive at towards the end of this article.

So let's begin our journey to DI nirvana.

Move the responsibility

Having identified the responsibility,  our next task is the get that sucka outta there!  We need to move the responsibility for creation of the Calender to outside this class.  The easiest way to achieve this at the moment is to add a new Calendar attribute to the ProductShipper class.


public class ProductShipperV2 {
    private static final float COST_PER_KILOGRAM = 1.25f;
    private static final float CHRISTMAS_LOADING = 2f;
    private Calendar calendar;
    ...

Inject the dependency

Now that the responsibility for creating the Calendar is no longer held by ProductShipper, all code that uses this class must supply a Calendar to the ProductShipper before use.  At the moment, ProductShipper has no mechanism for external classes to inject the dependency, so we'll use a setter method to allow this access.

    ...
    public void setCalendar(Calendar calendar) {
        this.calendar = calendar;
    }
    ...

Note that we could have achieved the same end result via a parameter to the constructor or a parameter to the calculateShippingCharges() method.  I've chosen to use a setter for no particular reason.

Extract interface

Now that we have a mechanism for other classes to inject the dependency into ProductShipper, we need to extract a Java interface out of the concrete implementation to provide us the ability to mock or stub the implementation at the appropriate moments.  Because there are two method calls being made against the Calendar attribute (one to retrieve the current month, one to retrieve the current day), we'll add two similar (but slightly more streamlined) methods to the new interface.  This leaves us with our ProductShipperCalendar interface shown below.

public interface ProductShipperCalendar {
    public int getMonth();
    public int getDay();
}

Remember that we haven't used the interface as yet - that's for the next step.

Change type

With our trusty ProductShipperCalendar interface in hand, we can now change all instances of Calendar in our ProductShipper to ProductShipperCalendar.  This will have the flow-in effect of needing to change the existing Calendar method calls to match those defined in our ProductShipperCalendar interface.  I've underlined the relevant changes in ProductShipper below.

import java.math.BigDecimal;
import java.util.Calendar;

public class ProductShipperV2 {
    private static final float COST_PER_KILOGRAM = 1.25f;
    private static final float CHRISTMAS_LOADING = 2f;
    private ProductShipperCalendar calendar;

    public BigDecimal calculateShippingCharges(Product product) {
        float weightInKilograms = product.getWeightInKilograms();
        if (weightInKilograms <= 0) {
            throw new IllegalArgumentException("Product has an invalid weight of " + weightInKilograms);    
        }

        BigDecimal charges = new BigDecimal(weightInKilograms * COST_PER_KILOGRAM);

        return charges.add(new BigDecimal(calculateAdditionalCharges()));
    }

    public void setCalendar(ProductShipperCalendar calendar) {
        this.calendar = calendar;
    }

    private float calculateAdditionalCharges() {
        if (duringChristmasPeriod()) {
            return CHRISTMAS_LOADING;
        }

        return 0f;
    }

    private boolean duringChristmasPeriod() {
        int month = calendar.getMonth();
        int day = calendar.getDay();

        return month == 11 && day > 23 && day < 31;
    }

}

Implement interface

Now that ProductShipper is using the ProductShipperCalendar interface, we can implement this interface to provide a configurable stub version where we can specify exactly which month and day will be returned.  As you can see below, I've done this simply by providing a couple of attributes for the day and month with accompanying getter and setter methods.


public class StubCalendar implements ProductShipperCalendar {
    private int month;
    private int day;

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }
}

Let's not forget that we'll need to provide a real implementation of the interface as well.  As you may have expected, this implemented simply delegates to Calendar itself.


import java.util.Calendar;

public class RealCalendar implements ProductShipperCalendar {
    public int getMonth() {
        return Calendar.getInstance().get(Calendar.MONTH);
    }

    public int getDay() {
        return Calendar.getInstance().get(Calendar.DATE);
    }
}

Test

Now that everything is wired up on the ProductShipper front, we can write a fairly simple JUnit test to exercise the code path for calculating Christmas shipping charges.  I've shown just one of these tests below - in reality, you would also need to test the various edge conditions around this logic.

    public void testCalculateChargesIncludesAdditionalChargesDuringChristmas() {
        StubCalendar calendar = initializeCalendar();
        productShipper.setCalendar(calendar);
        product.setWeightInKilograms(1.5f);

        assertEquals(new BigDecimal(1.875 2), productShipper.calculateShippingCharges(product));

    }

    private StubCalendar initializeCalendar() {
        StubCalendar calendar = new StubCalendar();
        calendar.setMonth(Calendar.DECEMBER);
        calendar.setDay(26);
        return calendar;
    }

The Final Product

We have now efficiently removed the dependency from our ProductShipper class that was preventing us testing the code using this dependency to calculate excess shipping charges.  Using the StubCalendar, we can now simulate running the ProductShipper code at any time during the year should more complex time-sensitive charging policies be added at a later date.  Some important points to note when writing the tests for ProductShipper:

All the source code (before and after) for this example can be found here.