Today's topic is Mock Test.
In the previous posts, I introduced bunch of test cases but all of target class is class which hits actual service or production environment.
However in the real world, in many cases we cannot simply use actual service class.
There are a lot of Mock libraries (especially in Java).
In this post, we use Mockito for explanation. You just downalod mockito-all-x.x.x.jar and include it to classpath, then you can use Mockito.
In the previous posts, I introduced bunch of test cases but all of target class is class which hits actual service or production environment.
However in the real world, in many cases we cannot simply use actual service class.
- Isolate from actual production service. ex. storing DB, calling external environment API.
- External API is too slow and we don't have to use acual data from the API but just would like to test clsasses which uses the API.
- Would like to controla data from API.
- Would like to test internal logic.
There are a lot of Mock libraries (especially in Java).
In this post, we use Mockito for explanation. You just downalod mockito-all-x.x.x.jar and include it to classpath, then you can use Mockito.
Mockito Example Code
I think you can easily understand how to use Mockito from the below code example.
package com.dukesoftware.exchangerate.service;
import static junit.framework.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.dukesoftware.exchangerate.api.ExchangeRateApi;
import com.dukesoftware.exchangerate.api.Rate;
import com.dukesoftware.exchangerate.service.PriceCalculator;
public class PriceCalculatorMockTest {
private PriceCalculator service;
private ExchangeRateApi mockApi;
@Before
public void setUp()
{
// !!KEY PART!!
// create mock
this.mockApi = Mockito.mock(ExchangeRateApi.class);
// inject
this.service = new PriceCalculator(this.mockApi);
this.service.initialize();
}
@After
public void tearDown()
{
this.service.shutdown();
}
// 1) use mock as stub
@Test
public void testCalculatePrice()
{
Rate rate = new Rate();
rate.setCcy1("USD");
rate.setCcy1("JPY");
rate.setValue(99d);
// !!KEY PART!!
// define the object which will be returned in the test.
// the below line means getRate method in api is called with USD, JPY, then return rate object user defined.
Mockito.when(mockApi.getRate("USD", "JPY")).thenReturn(rate);
double priceJPY = this.service.calculatePrice(1, "USD", "JPY");
assertEquals(103.95, priceJPY, 0.0001);
}
// 2) cache mechanism works fine?
@Test
public void testCacheMechanism()
{
// setup mock values on api
// rate value is just dummy
setUpRateOnMockApi("JPY", "USD", 0.0102);
setUpRateOnMockApi("CHF", "USD", 0.0102);
setUpRateOnMockApi("AUD", "USD", 0.0102);
setUpRateOnMockApi("HKD", "USD", 0.0102);
// execute
this.service.calculatePrice(10000, "JPY", "USD");
this.service.calculatePrice(10000, "JPY", "USD");
this.service.calculatePrice(10000, "CHF", "USD");
this.service.calculatePrice(10000, "CHF", "USD");
this.service.calculatePrice(10000, "AUD", "USD");
this.service.calculatePrice(10000, "AUD", "USD");
this.service.calculatePrice(10000, "HKD", "USD");
this.service.calculatePrice(10000, "HKD", "USD");
// !!KEY PART!!
// verify
// check the method getRate is called only one time for each currency combination.
Mockito.verify(mockApi, Mockito.times(1)).getRate("JPY", "USD");
Mockito.verify(mockApi, Mockito.times(1)).getRate("CHF", "USD");
Mockito.verify(mockApi, Mockito.times(1)).getRate("AUD", "USD");
Mockito.verify(mockApi, Mockito.times(1)).getRate("HKD", "USD");
}
// helper method
private void setUpRateOnMockApi(String ccy1, String ccy2, double value)
{
Rate rate = new Rate();
rate.setCcy1(ccy1);
rate.setCcy1(ccy2);
rate.setValue(value);
Mockito.when(mockApi.getRate(ccy1, ccy2)).thenReturn(rate);
}
// 3) using any
@Test
public void testCacheMechanismShorter()
{
Rate rate = new Rate();
rate.setValue(1d);
// using any
Mockito.when(mockApi.getRate(
// either way is fine....
Mockito.anyString(),
Mockito.<String>any())
).thenReturn(rate);
// execute
this.service.calculatePrice(10000, "JPY", "USD");
this.service.calculatePrice(10000, "JPY", "USD");
this.service.calculatePrice(10000, "CHF", "USD");
this.service.calculatePrice(10000, "CHF", "USD");
this.service.calculatePrice(10000, "AUD", "USD");
this.service.calculatePrice(10000, "AUD", "USD");
this.service.calculatePrice(10000, "HKD", "USD");
this.service.calculatePrice(10000, "HKD", "USD");
// verify
// should be called only one time
Mockito.verify(mockApi, Mockito.times(1)).getRate("JPY", "USD");
Mockito.verify(mockApi, Mockito.times(1)).getRate("CHF", "USD");
Mockito.verify(mockApi, Mockito.times(1)).getRate("AUD", "USD");
Mockito.verify(mockApi, Mockito.times(1)).getRate("HKD", "USD");
}
}
You need PriceCalculator class, Rate class and ExchangeRateApi interface if you would like to run above code example.
package com.dukesoftware.exchangerate.service;
import java.util.HashMap;
import java.util.Map;
import com.dukesoftware.exchangerate.api.ExchangeRateApi;
import com.dukesoftware.exchangerate.api.Rate;
public class PriceCalculator {
private final ExchangeRateApi api;
private final Map<String, Rate> map = new HashMap<>();
private final static double FEE_RATE = 0.05;
public PriceCalculator(ExchangeRateApi api) {
this.api = api;
}
public void initialize()
{
}
public void shutdown()
{
this.map.clear();
}
public double calculatePrice(double price, String ccy1, String ccy2)
{
Rate rate = this.map.get(ccy1+":"+ccy2);
if(rate == null)
{
// caching
rate = this.api.getRate(ccy1, ccy2);
this.map.put(ccy1+":"+ccy2, rate);
}
return rate.getValue() * (price * (1 + FEE_RATE));
}
}
package com.dukesoftware.exchangerate.api;
public class Rate {
private String ccy1;
private String ccy2;
private Double value;
private String date;
public String getCcy1() {
return ccy1;
}
public void setCcy1(String ccy1) {
this.ccy1 = ccy1;
}
public String getCcy2() {
return ccy2;
}
public void setCcy2(String ccy2) {
this.ccy2 = ccy2;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
@Override
public String toString() {
return "ccy1:"+ccy1+",ccy2:"+ccy2+",date:" + date + ",value:" + value;
}
}
コメント