Why mocks matter

July 19, 2011

Unit testing software is important. Test-driven development tells us to unit test our software before it is even written. Unfortunately this is not always possible, since a developer often spends more time maintaining exiting code than writing new code. When code has been written outside the scope of test-driven development, it tends to be spaghetti, and building new tests for old code encompasses a spectrum of challenges ranging from "tricky" to "nuke the site from orbit".

The most consistent problem I encounter when writing tests for legacy code is the inherent self-coupling that the code exhibits; it can be so dependent on itself that it is impossible to divide it into logical chunks that can be independently tested. Often, large portions of the application must be spun up to test even the simplest piece of functionality. This is where mock objects can make life easier.

To unit test software is to verify the proper function of an arbitrarily small, independent, piece of code. Whatever the scope of the test, it must be clearly defined so that external dependencies are mocked appropriately to prevent their impact on the execution of the code under test.

Consider the following code:

public class CurrencyConverter {

  private final ExchangeRateRepository exchangeRateRepository;

  public CurrencyConverter(ExchangeRateRepository _exchangeRateRepository) {
    exchangeRateRepository = _exchangeRateRepository;
  }

  public double convert(Currency from, Currency to, double amount) {
    return exchangeRateRepository.getExchangeRate(from, to) * amount;
  }
}

In this (contrived) example, a CurrencyConverter converts between two currencies based on some exchange rate retrieved via an ExchangeRateRepository. To unit test CurrencyConverter, the calculation within convert is under scrutiny, but the function of ExchangeRateRepository is not. It would be a mistake to inject an instance of ExchangeRateRepository into the CurrencyConverter constructor for unit testing.

This mistake can be avoided with a mock object. By mocking the behavior of a ExchangeRateRepository, we can be certain that no errors can be introduced into our test by any implementation of a ExchangeRateRepository. The mocked behavior of CurrencyConverter's dependencies will be sandboxed in the unit test, and only the code under test will be under test.

Consider the following unit test:

@Test
public void convertShouldMultiplyTheExchangeRateByTheAmount() {
  ExchangeRateRepository exchangeRateRepository = Mockito.mock(ExchangeRateRepository.class);
  Mockito.when(exchangeRateRepository.getExchangeRate(Mockito.any(Currency.class), Mockito.any(Currency.class))).thenReturn(1.5);

  Currency us = Mockito.mock(Currency.class);
  Currency gb = Mockito.mock(Currency.class);

  CurrencyConverter currencyConverter = new CurrencyConverter(exchangeRateRepository);
  Assert.assertEquals(4.5, currencyConverter.convert(gb, us, 3.0));
}

In this simple test, the behavior of everything external to CurrencyConverter has been mocked. The mock ExchangeRateRepository will return 1.5 when asked for any exchange rate, allowing the test to focus on the multiplication of the expected exchange rate by the given amount. All external dependencies have been decoupled from the unit test, and the scope is kept nice and tight.

Of the many mock objects frameworks, I have found Mockito to provide the simplest API. It is powerful enough to handle strange edge cases, and simple enough to be coherent in the majority of test cases.