為神馬要使用Mockito?
在編寫單元測試的時候,為了盡可能的保證隔離性,我們時常需要對某些不容易構造或者不容易獲取或者對外部環境有依賴的對象,用一個虛擬的對象來創建以便於測試.假設你正在開發的的代碼中使用到了公司其他部門的接口(通過RPC服務),當編寫單元測試的時候你可能為了不讓接口真的去調用rpc服務而mock一個接口的對象,最原始的方式是自己手工編寫一個該接口的實現類,並且在單元測試的時候注入這個對象,而使用Mockito則可以讓我們方便地創建和配置mock對象,使用mockito可以簡化對外部環境的依賴.
創建mock對象
這里我們以一個操作Redis的工具類來舉例,下面是代碼:
public class RedisUtil { @Autowired private RedisOperations<String, String> redisTemplate; public boolean checkKeyExists(String key) { return redisTemplate.hasKey(key); } public void setValueByKey(String key, String value) { redisTemplate.opsForValue().set(key, value); }
public String getValueByKey(String key) { return redisTemplate.opsForValue().get(key); } public List<String> getMutiValuesByList(List<String> keys){ return redisTemplate.opsForValue().multiGet(keys); } public void deleteKey(String key) { redisTemplate.delete(key); } public void increValue(String key,Long count){ redisTemplate.opsForValue().increment(key,count); } }
有兩種方式可以方便的創建mock對象,第一種方式是
Mockito.mock(RedisUtil.class);
還有一種方式在注入時使用@mock注解:
@mock private RedisUtil redisUtil;
Tips:如果在代碼中頻繁的使用Mockito比較煩,可以靜態導入package,以下例子全部默認已靜態導入Mockito包:
import static org.mockito.Mockito.*;
為測試函數打樁的常用方法
當對象被創建之后,就可以對代碼中出現的方法進行自定義的交互,mock對象會記住這些交互,在單元測試的執行中碰到代碼中的對應方法會默認執行被你自定義的方法內容.
還是以RedisUtil為例,對方法設定返回值:
when(redisUtil.getValueByKey("key1")).thenReturn("value1");
when(redisUtil.getValueByKey("key2")).thenReturn("value2");
對方法設定返回自定義的異常信息:
when(redisUtil.getValueByKey("key1")).thenThrow(new RuntimeException);
此外Mockito還支持迭代風格的返回值定義:
when(redisUtil.getValueByKey("key1")).thenReturn("value1").thenReturn("Value2");
即當方法第一次調用redis.getValueBykey("key1")時會返回value1,當再次被調用時則會返回value2.這里需要注意的是,當后續再出現調用的時候返回值都會是value2,而且這種迭代風格的定義支持return和Throw的混搭,即你可以控制在函數調用的第一次去拋出一個異常,而在函數調用的第二次繪制一個正常的值.
Mockito如何mock返回值為void的方法
首先,測試中對於返回值為void 的方法進行mock本身是沒有什么效果的,Mockito有一個doNothing方法是void方法的默認返回:
doNothing().when(redisUtil).increValue(“key1",1L);
其實這里使用doNothing來mock這個方法並沒有什么意義,因為我們mock一個方法的目的無非有兩個,第一,在某一中輸入環境中模擬返回我們期待的返回值,第二就是當方法拋出異常時能夠在我們預期控制之下而不會導致單元測試失敗,因此對於返回值為void的方法,我們一般可以不去mock它或者使用doThrow()來為void函數打一個樁,當出現異常的時候mock他的異常返回,當不會有異常發生時,只需要在調用后,verify()一下,驗證方法的被調用次數即可.
verify(redisUtil,times(1)).increValue("key1",1L);
代碼中的times(1)表示一次,即代碼中increValue()返回被調用一次的時候能夠通過,還可以支持更加廣泛的定義,
never():表示從未被調用
atleastOnce():表示至少被調用一次
atleast(3):表示至少被調用3次
atMost(7):表示最多被調用7次
參數匹配器
這里主要介紹一下內置的幾個參數匹配器,其實也很好理解,還是那上面的redisUtil為例,對於redisUtil.getValueByKey來說,我希望對於任意的key都返回同一個值,那就可以這么寫:
when(redisUtil.getValueByKey(anyString()).thenReturn("value1")
這樣在單元測試過程中,對於任意的輸入參數,該方法都會返回value1,相同的類型還有很多anyLong(),anyInt(),anyList()等等
使用Spy對象來監控真實對象
以上所講的對象都是mock對象,mock對象只能調用打樁方法,不能調用真實方法,使用Spy可以讓我們能夠監視一個真實對象,既可以對這個對象的某一個函數打樁返回我們期望的值,也可以去調用真實的方法,創建spy對象的方式和mock類似,不同的一點是spy需要傳一個真實對象而不是一個CLass對象.這里以一個List為例,
List spy = spy(new LinkedList()); when(spy.get(0)).thenReturn("value1"); doReturn("value2").when(spy).get(0);
上面第二行代碼,調用when(spy.get(0)),會去調用真實的方法,會拋出異常,第三行代碼則不會去調用真實方法,而返回value2.所以總結一下就是,當使用when去模擬返回值的時候,真是方法會被調用,而是用doReturn()去設置的話,則不會去執行真實方法.
需要注意在使用時應該盡量避免使用spy.
參考文檔