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; } }
コメント