单元测试之带你搞懂Mockito使用
Mock介紹
在平時開發過程中,我們往往會遇到以下問題
1.由于依賴調用的接口沒有開發完成,需要等待(客戶端和服務端,服務端和其他服務之間)
2.自測時由于服務器故障等無法正常調用接口,或者一些邊界條件無法在測試環境模擬數據
3.同樣的單元測試,當依賴的數據發生變化時,無法反復執行,不能在上線前對之前的功能進行自動回歸
mock就幫我們解決了以上問題
mock的定義(what):
mock是在測試過程中,對于一些不容易構造/獲取的對象,創建一個mock對象來模擬對象的行為
哪些時機和場合需要使用mock(when&where):
1.單元測試/接口測試中測試對象依賴其他對象,這些對象的構造復雜、耗時或者根本無法構造(未交付)
2.我們只測試對象內部邏輯的質量,不關心依賴對象的邏輯正確性和穩定性
3.一些邊界條件無法在正常情形下模擬 比方說接口異常返回
使用mock的時候并不需要對所有的依賴服務(對象)都進行Mock,只需要對不容易構造的對象或者不穩定的對象進行mock可以了。當然,mock的前提是底層服務提供的數據是值得信任的,實際開發過程還是需要進行聯調測試的。
像EasyMock , Mockito , PowerMock都是常用的mock框架
powerMock是基于easyMock或Mockito擴展出來的增強版本,能夠mock靜態、final、私有方法等,這些都是EasyMock和Mockito不能做到的。
Mockito和EasyMock的功能差不多,但是Mockito不需要錄制、播放這些動作,語法上比EasyMock更靈活,可讀性更好
個人比較喜歡Mockito和PowerMock
今天就先來簡單講講關于Mockito的使用,之后會單獨再講下PowerMock的增強點,就是對于靜態、final、私有方法的mock
Mockito使用
1.Maven項目,需要先引入如下pom
<dependency><groupId>org.mockito</groupId><artifactId>mockito-all</artifactId><version>1.9.5</version><scope>test</scope> </dependency>你也可以使用mockito-core的pom,不過mockito-core里只包含mockito類,而mockito-all會包含mockito類以及一些依賴項,比方說hamcrest。
實際上mockito-all已經停止更新,Mockito 2中已停止使用“mockito-all”發行版。*。
這里我使用的是mockito-all,兩個pom創建代理對象的方式不一樣,Mockito1是通過CGlib,而Mockito2是使用的ByteBuddy,常用的api都沒有區別,我后面提到的用法兩個pom都支持
2.@Mock和@InjectMocks注解
@Mock : 為某個類創建Mock對象,使用方式如下(通常直接加在屬性字段上):
@Mockprivate GetAirportTransferOrderHandler getAirportTransferOrderHandler;等價于
GetAirportTransferOrderHandler singleMock = Mockito.mock(GetAirportTransferOrderHandler.class);@InjectMocks
@InjectMocks - injects mock or spy fields into tested object automatically.
也就是說為被測試對象自動注入mock或者spy的字段 (有點類似于spring的依賴注入的感覺),注入的方式有三種:構造方法注入,setter注入,屬性注入
3.Mockito初始化的方式
官方文檔上的意思就是如果使用了@Mock和@InjectMocks注解,那么我們就需要調用MockitoAnnotations.initMocks(testClass)來幫助我們完成mock對象的創建和自動注入
Mockito初始化的方式有三種:
//方式一 @Before public void init() {MockitoAnnotations.initMocks(this); } //方式二 @RunWith(MockitoJUnitRunner.class) //方式三 @Rule public MockitoRule mockito = MockitoJUnit.rule()三者的使用效果都是一樣的,主要是為了
1.提供mock初始化工作
2.為unit test提供框架使用的自動驗證
使用方式一或者方式三的好處在于不需要使用Mockito的Runner,就可以使用其他的Runner了,比方說SpringJUnit4ClassRunner
4.Stubbing 插樁
設置mock對象的某個方法返回期望值
when(mockedObject.method() ).thenReturn( expectValue);
對于沒有stub過的有返回值的方法,會返回默認值(0,false,null等)
@RunWith(MockitoJUnitRunner.class) public class GetAirportTransferOrderListCiTest {@InjectMocksprivate GetAirportTransferOrderListProcessor getAirportTransferOrderListProcessor;@Mockprivate GetAirportTransferOrderHandler getAirportTransferOrderHandler;@Testpublic void testGetAirportTransferOrderList() throws Throwable {GetAirportTransferOrderListRequestType getAirportTransferOrderListRequestType = new GetAirportTransferOrderListRequestType();getAirportTransferOrderListRequestType.setUid("test");getAirportTransferOrderListRequestType.setLocale("zh-HK");getAirportTransferOrderListRequestType.setOrderIds(Lists.newArrayList(1212));when(getAirportTransferOrderHandler.handle(anyObject())).thenReturn(getAirportTransferOrderList());GetAirportTransferOrderListResponseType responseType = getAirportTransferOrderListProcessor.execute(getAirportTransferOrderListRequestType);Assert.assertTrue(responseType.getOrderInfos().size() == 1);}可以調用方法時模擬拋出異常,適合于模擬第三方接口或者服務故障情形
when(RedisManager.getInstance()).thenThrow(new Exception(“error get redis connection”));
對于沒有返回值的方法,可以通過如下方式來進行插樁
Mockito.doNothing().when(mockObject).voidMethod(param);
也可以使用doAnswer來插樁
Answer由于可以獲取到方法調用的參數信息,使用doAnswer我們還可以根據方法的調用參數返回不同的結果
doAnswer(new Answer() {public Object answer(InvocationOnMock invocation) {String username = (String) invocation.getArguments()[0];if(!StringUtils.isEmpty(username)){return "有姓名";}return "沒有姓名";}}).when(userService).show(anyString(),anyString());5.Argument Matchers 參數匹配
Mockito的參數匹配有兩種方式:
1.傳入實際的參數
判斷是否匹配的時候直接通過equals方法進行比較
when(commonService.getCityName(2,“en-US”)).thenReturn(“shanghai”);
2.使用arguments matchers對象
eg.anyInt(),anyString(),anyObject(),anySet(),eq()等
下面的參數匹配方式就是說明,對于調用getAirportTransferOrderHandler的handle方法,無論是什么參數都返回getAirportTransferOrderList()的數據結果
when(getAirportTransferOrderHandler.handle(anyObject())).thenReturn(getAirportTransferOrderList());
注意上述兩種參數匹配方式是不能混用的
when(commonService.getCityName(eq(2),“en-US”)).thenReturn(“shanghai”); 錯誤
when(commonService.getCityName(eq(2),eq(“en-US”)).thenReturn(“shanghai”); 正確
when(commonService.getCityName(eq(2),anyString()).thenReturn(“shanghai”); 正確
如果我們需要自己定義參數的驗證規則,Mockito還提供了custom argument matchers – argThat
使用如下:
6.verify 驗證mock方法被調用了特定次數/至少x次/最多x次/從未被調用
//是否add("twice")被調用了兩次。 verify(mockedList, times(2)).add("twice"); //驗證add("twice")被調用了至少一次 等價于verify(mockedList).add("twice"); verify(mockedList, atLeastOnce()).add("twice"); verify(mockedList, atLeast(2)).add("twice"); verify(mockedList, atMost(5)).add("twice"); verify(mockedList, never()).add("twice");我在開發過程中有時會使用verify來驗證緩存是否生效,調用兩次代碼,如果mock只被執行了一次,說明第二次緩存命中
7.Spy
You can create spies of real objects. When you use the spy then the real methods are called (unless a method was stubbed).
關于spy官方的定義寫的很清楚,spy是用于創建對應的真實對象,如果對應的方法我們自己不主動進行插樁的話就會執行對應的真實業務代碼
對于Spy的對象,他調用的都是真實方法,如果你想讓他返回的值按照自己設定的來,就需要自己去mock
適合于對象內有大量的調用了大量的方法,但實際只需要mock關注的少量方法即可
看下我實際使用的一個例子:
使用@Mock生成的類,默認所有方法都不是真實的方法,而且返回值都是NULL。
使用@Spy生成的類,默認所有方法都是真實方法,返回值都是和真實方法一樣的。
當用when去設置mock返回值時,它里面的方法(cache.get(XX))會先執行一次。使用doReturn去設置的話,就不會產生上面的問題
所以如果需要對Spy的對象進行mock的時候,推薦都直接使用doReturn或者doThrow的句式,否則就會先調用一次真實的業務邏輯。如果你的數據有些初始化操作沒有執行,那么就可能出現異常
像下面這種情形,由于spy的List并沒有添加任何元素,直接執行spy.get(0)就會發生數組越界問題
List list = new LinkedList();List spy = spy(list);//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)when(spy.get(0)).thenReturn("foo");//You have to use doReturn() for stubbingdoReturn("foo").when(spy).get(0);注意:由于@Spy是監控的真實對象,我們需要先得到一個真實的對象,才可以對它使用spy的功能。一般可以通過spring容器幫我們生成bean或者自己手動new創建對象
8.同一個mock多次調用相同方法返回不同的結果
Mockito.when(methodCall).thenReturn(result1).thenReturn(result2).thenReturn(resultx) //簡化寫法 Mockito.when(methodCall).thenReturn(result1,result2,resultx)這個適合于需要mock迭代器的場景,或者同樣的方法在執行過程中需要多次調用,當然對于這種情形是可以通過不同的參數匹配和doAnswer來解決的
9.鏈式調用的mock
一般情形下我們都是創建一個mock對象,然后對其進行插樁,但是有的的情形下我們可能需要mock一個鏈式調用的對象,比方說建造者模式下的Builder
我們可以這樣寫
對于一兩次的鏈式調用還好,如果次數多了,mock起來就會比較麻煩,Mockito提供了Deep Stub的功能
上面的寫法等價于下面的
參考文檔:
https://javadoc.io/doc/org.mockito/mockito-core/2.26.0/org/mockito/Mockito.html
翻譯版:
Mockito 中文文檔 ( 2.0.26 beta )
總結
以上是生活随笔為你收集整理的单元测试之带你搞懂Mockito使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty学习笔记(六)Pipeline
- 下一篇: 单元测试之更强大的powermock