The Test-Driven Development (TDD) Mindset (2/2)

In my previous article, we explored the train of thoughts when designing a component using test-driven development (TDD) to convert money from USD to COP. In this second part, I’ll add some additional design and introduce the use of doubles.

Writing Code
Writing Code

Until now, we have designed a MoneyConverter component with a method convertUSDtoCOP() that uses a fixed exchange rate passed as a constructor argument to make the conversion. Obviously, the conversion rate will vary and we want to obtain the current conversion rate so that we can get real results.
Following the Single Responsibility Principle, we would like another component to take care of obtaining the latest exchange rate between both currencies.

Introducing collaborators

Given I’m currently designing the MoneyConverter using test-driven development, the collaborators will be introduced as doubles (commonly referred as mocks). For this, I’ll add the Mockito dependency to my project, as this is my library of choice when it comes to mocking in java. For the example, I’ll add it to the pom.xml file:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.8.47</version>
</dependency>

Then, I imagine this collaborator as a service, which I’ll call ExchangeRateService. I’ll declare a mock on my test to start using it.

@Mock private ExchangeRateService mockedExchageRateService; 

In order to use this mock, I’ll have to initialize it during setup():

MockitoAnnotations.initMocks(this);

The IDE will immediately complain about ExchangeRateService not existing. Let the IDE create that for you, as an interface. Why an interface? It could be a concrete class, but I prefer to work on an interface at this point because I want to focus my design efforts in the protocol used to collaborate with that object, I mean, what I’m going to send and what I’m going to receive from it. When I start designing the ExchangeRateService component, it’ll be easier to implement this interface to respect the protocol I’m going to establish right now. Here’s what the IDE produced:

package net.velocitypartners.money;

public interface ExchangeRateService {
}

Obtaining the exchange rate

The next step is thinking how I would like to obtain the exchange rate from the service. I think I’ll call a method named getExchangeRate(String fromCurrencyCode, String toCurrencyCode), so that I can pass a pair of currency codes and expect the result as a BigDecimal. I’m deciding that this service should handle any currency as parameters, although I’ll pass only the ones I’m interested right now, COP and USD. This is because I plan to wrap an external service with this component, so I leave the API open for the future. Let’s add this to the interface so that it looks like this:

public interface ExchangeRateService {
    BigDecimal getExchangeRate(String fromCurrencyCode, String toCurrencyCode);
}

How frequently should I update the exchange rate?

 

Currency
Currency

The frequency of updates is another design decision. For this simple example, I’ll go with getting the exchange rate on every call, and I’ll express this through my test. This may not be the best performant decision, but it’ll work for the example. So, let’s start by modifying the setup method of the test to send the ExchangeRateService in the MoneyConverter constructor instead of the actual rate. The IDE will complain about the inexistence of a constructor with such param, so we ask it to change the actual constructor. This will break the constructor, but for now, I’ll just comment the code in the constructor so I can continue setting up the test. I’ll also remove the usdToCopExchangeRate, that it’s of no use anymore. My setup method looks like this:

 

@Before public void setup(){ 
    MockitoAnnotations.initMocks(this);  
    converter = new MoneyConverter(mockedExchangeRateService); 
}

Now, let’s change the test: in the “Given” section, I’ll stub a value for when the exchange rate service is called, in this case, I’ll keep the exchange rate of 2920.82 pesos per dollar from the previous article, so I don’t have to modify my assertion. The “when” section will be the same, calling the convertUSDtoCOP method. In the “Then” section, I want to verify that my component interacts with the ExchangeRateService the way I decided, so every time I make a call to convert money, it should call the service to get the exchange rate. I’ll use Mockito’s verify for this. The final code will look like this:

@Test public void itConvertsUSDtoCOP(){ 
    //Given 
    BigDecimal amountToConvert = new BigDecimal("10.00"); 
    when(mockedExchangeRateService.getExchangeRate("USD", "COP")).thenReturn(new BigDecimal("2920.82")); 
    //When 
    BigDecimal result = converter.convertUSDtoCOP(amountToConvert); 
    //Then 
    assertEquals(new BigDecimal("29208.20"), result); 
    verify(mockedExchangeRateService).getExchangeRate("USD", "COP"); 
}

So we have our failing test, expressing our design. We can go ahead and implement the changes to make the test pass:

public class MoneyConverter { 
    private static final int SCALE = 2;  
    private ExchangeRateService exchangeRateService;

    public MoneyConverter(ExchangeRateService exchangeRateService) { 
        this.exchangeRateService = exchangeRateService; 
    }

    public BigDecimal convertUSDtoCOP(BigDecimal amountInUSD) { 
        BigDecimal usdToCopExchangeRate = exchangeRateService.getExchangeRate("USD", "COP"); 
        return amountInUSD.multiply(usdToCopExchangeRate).setScale(SCALE); 
    }
}

Now we run the test and bingo! Green test. We have successfully coded what we designed through the test. You can check the code for this article in my GitHub account, checking out the “part2” branch.

Closing

I hope these two articles expose the idea of how to apply test driven development thinking to your project. Remember that although they are tests, the real purpose is to create the API and express the relation and communication protocol between components. A good knowledge of the tools and libraries used will help you feel at home when applying this technique. Happy test-driven development!

Adrian Moya

Adrian Moya

Agile Software Developer, Linux & Opensource evangelist. Cloud Computing passionate.