单元测试之带你搞懂Mockito使用
Mock介紹
在平時(shí)開發(fā)過程中,我們往往會(huì)遇到以下問題
1.由于依賴調(diào)用的接口沒有開發(fā)完成,需要等待(客戶端和服務(wù)端,服務(wù)端和其他服務(wù)之間)
2.自測時(shí)由于服務(wù)器故障等無法正常調(diào)用接口,或者一些邊界條件無法在測試環(huán)境模擬數(shù)據(jù)
3.同樣的單元測試,當(dāng)依賴的數(shù)據(jù)發(fā)生變化時(shí),無法反復(fù)執(zhí)行,不能在上線前對之前的功能進(jìn)行自動(dòng)回歸
mock就幫我們解決了以上問題
mock的定義(what):
mock是在測試過程中,對于一些不容易構(gòu)造/獲取的對象,創(chuàng)建一個(gè)mock對象來模擬對象的行為
哪些時(shí)機(jī)和場合需要使用mock(when&where):
1.單元測試/接口測試中測試對象依賴其他對象,這些對象的構(gòu)造復(fù)雜、耗時(shí)或者根本無法構(gòu)造(未交付)
2.我們只測試對象內(nèi)部邏輯的質(zhì)量,不關(guān)心依賴對象的邏輯正確性和穩(wěn)定性
3.一些邊界條件無法在正常情形下模擬 比方說接口異常返回
使用mock的時(shí)候并不需要對所有的依賴服務(wù)(對象)都進(jìn)行Mock,只需要對不容易構(gòu)造的對象或者不穩(wěn)定的對象進(jìn)行mock可以了。當(dāng)然,mock的前提是底層服務(wù)提供的數(shù)據(jù)是值得信任的,實(shí)際開發(fā)過程還是需要進(jìn)行聯(lián)調(diào)測試的。
像EasyMock , Mockito , PowerMock都是常用的mock框架
powerMock是基于easyMock或Mockito擴(kuò)展出來的增強(qiáng)版本,能夠mock靜態(tài)、final、私有方法等,這些都是EasyMock和Mockito不能做到的。
Mockito和EasyMock的功能差不多,但是Mockito不需要錄制、播放這些動(dòng)作,語法上比EasyMock更靈活,可讀性更好
個(gè)人比較喜歡Mockito和PowerMock
今天就先來簡單講講關(guān)于Mockito的使用,之后會(huì)單獨(dú)再講下PowerMock的增強(qiáng)點(diǎn),就是對于靜態(tài)、final、私有方法的mock
Mockito使用
1.Maven項(xiàng)目,需要先引入如下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會(huì)包含mockito類以及一些依賴項(xiàng),比方說hamcrest。
實(shí)際上mockito-all已經(jīng)停止更新,Mockito 2中已停止使用“mockito-all”發(fā)行版。*。
這里我使用的是mockito-all,兩個(gè)pom創(chuàng)建代理對象的方式不一樣,Mockito1是通過CGlib,而Mockito2是使用的ByteBuddy,常用的api都沒有區(qū)別,我后面提到的用法兩個(gè)pom都支持
2.@Mock和@InjectMocks注解
@Mock : 為某個(gè)類創(chuàng)建Mock對象,使用方式如下(通常直接加在屬性字段上):
@Mockprivate GetAirportTransferOrderHandler getAirportTransferOrderHandler;等價(jià)于
GetAirportTransferOrderHandler singleMock = Mockito.mock(GetAirportTransferOrderHandler.class);@InjectMocks
@InjectMocks - injects mock or spy fields into tested object automatically.
也就是說為被測試對象自動(dòng)注入mock或者spy的字段 (有點(diǎn)類似于spring的依賴注入的感覺),注入的方式有三種:構(gòu)造方法注入,setter注入,屬性注入
3.Mockito初始化的方式
官方文檔上的意思就是如果使用了@Mock和@InjectMocks注解,那么我們就需要調(diào)用MockitoAnnotations.initMocks(testClass)來幫助我們完成mock對象的創(chuàng)建和自動(dòng)注入
Mockito初始化的方式有三種:
//方式一 @Before public void init() {MockitoAnnotations.initMocks(this); } //方式二 @RunWith(MockitoJUnitRunner.class) //方式三 @Rule public MockitoRule mockito = MockitoJUnit.rule()三者的使用效果都是一樣的,主要是為了
1.提供mock初始化工作
2.為unit test提供框架使用的自動(dòng)驗(yàn)證
使用方式一或者方式三的好處在于不需要使用Mockito的Runner,就可以使用其他的Runner了,比方說SpringJUnit4ClassRunner
4.Stubbing 插樁
設(shè)置mock對象的某個(gè)方法返回期望值
when(mockedObject.method() ).thenReturn( expectValue);
對于沒有stub過的有返回值的方法,會(huì)返回默認(rèn)值(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);}可以調(diào)用方法時(shí)模擬拋出異常,適合于模擬第三方接口或者服務(wù)故障情形
when(RedisManager.getInstance()).thenThrow(new Exception(“error get redis connection”));
對于沒有返回值的方法,可以通過如下方式來進(jìn)行插樁
Mockito.doNothing().when(mockObject).voidMethod(param);
也可以使用doAnswer來插樁
Answer由于可以獲取到方法調(diào)用的參數(shù)信息,使用doAnswer我們還可以根據(jù)方法的調(diào)用參數(shù)返回不同的結(jié)果
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 參數(shù)匹配
Mockito的參數(shù)匹配有兩種方式:
1.傳入實(shí)際的參數(shù)
判斷是否匹配的時(shí)候直接通過equals方法進(jìn)行比較
when(commonService.getCityName(2,“en-US”)).thenReturn(“shanghai”);
2.使用arguments matchers對象
eg.anyInt(),anyString(),anyObject(),anySet(),eq()等
下面的參數(shù)匹配方式就是說明,對于調(diào)用getAirportTransferOrderHandler的handle方法,無論是什么參數(shù)都返回getAirportTransferOrderList()的數(shù)據(jù)結(jié)果
when(getAirportTransferOrderHandler.handle(anyObject())).thenReturn(getAirportTransferOrderList());
注意上述兩種參數(shù)匹配方式是不能混用的
when(commonService.getCityName(eq(2),“en-US”)).thenReturn(“shanghai”); 錯(cuò)誤
when(commonService.getCityName(eq(2),eq(“en-US”)).thenReturn(“shanghai”); 正確
when(commonService.getCityName(eq(2),anyString()).thenReturn(“shanghai”); 正確
如果我們需要自己定義參數(shù)的驗(yàn)證規(guī)則,Mockito還提供了custom argument matchers – argThat
使用如下:
6.verify 驗(yàn)證mock方法被調(diào)用了特定次數(shù)/至少x次/最多x次/從未被調(diào)用
//是否add("twice")被調(diào)用了兩次。 verify(mockedList, times(2)).add("twice"); //驗(yàn)證add("twice")被調(diào)用了至少一次 等價(jià)于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");我在開發(fā)過程中有時(shí)會(huì)使用verify來驗(yàn)證緩存是否生效,調(diào)用兩次代碼,如果mock只被執(zhí)行了一次,說明第二次緩存命中
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).
關(guān)于spy官方的定義寫的很清楚,spy是用于創(chuàng)建對應(yīng)的真實(shí)對象,如果對應(yīng)的方法我們自己不主動(dòng)進(jìn)行插樁的話就會(huì)執(zhí)行對應(yīng)的真實(shí)業(yè)務(wù)代碼
對于Spy的對象,他調(diào)用的都是真實(shí)方法,如果你想讓他返回的值按照自己設(shè)定的來,就需要自己去mock
適合于對象內(nèi)有大量的調(diào)用了大量的方法,但實(shí)際只需要mock關(guān)注的少量方法即可
看下我實(shí)際使用的一個(gè)例子:
使用@Mock生成的類,默認(rèn)所有方法都不是真實(shí)的方法,而且返回值都是NULL。
使用@Spy生成的類,默認(rèn)所有方法都是真實(shí)方法,返回值都是和真實(shí)方法一樣的。
當(dāng)用when去設(shè)置mock返回值時(shí),它里面的方法(cache.get(XX))會(huì)先執(zhí)行一次。使用doReturn去設(shè)置的話,就不會(huì)產(chǎn)生上面的問題
所以如果需要對Spy的對象進(jìn)行mock的時(shí)候,推薦都直接使用doReturn或者doThrow的句式,否則就會(huì)先調(diào)用一次真實(shí)的業(yè)務(wù)邏輯。如果你的數(shù)據(jù)有些初始化操作沒有執(zhí)行,那么就可能出現(xiàn)異常
像下面這種情形,由于spy的List并沒有添加任何元素,直接執(zhí)行spy.get(0)就會(huì)發(fā)生數(shù)組越界問題
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是監(jiān)控的真實(shí)對象,我們需要先得到一個(gè)真實(shí)的對象,才可以對它使用spy的功能。一般可以通過spring容器幫我們生成bean或者自己手動(dòng)new創(chuàng)建對象
8.同一個(gè)mock多次調(diào)用相同方法返回不同的結(jié)果
Mockito.when(methodCall).thenReturn(result1).thenReturn(result2).thenReturn(resultx) //簡化寫法 Mockito.when(methodCall).thenReturn(result1,result2,resultx)這個(gè)適合于需要mock迭代器的場景,或者同樣的方法在執(zhí)行過程中需要多次調(diào)用,當(dāng)然對于這種情形是可以通過不同的參數(shù)匹配和doAnswer來解決的
9.鏈?zhǔn)秸{(diào)用的mock
一般情形下我們都是創(chuàng)建一個(gè)mock對象,然后對其進(jìn)行插樁,但是有的的情形下我們可能需要mock一個(gè)鏈?zhǔn)秸{(diào)用的對象,比方說建造者模式下的Builder
我們可以這樣寫
對于一兩次的鏈?zhǔn)秸{(diào)用還好,如果次數(shù)多了,mock起來就會(huì)比較麻煩,Mockito提供了Deep Stub的功能
上面的寫法等價(jià)于下面的
參考文檔:
https://javadoc.io/doc/org.mockito/mockito-core/2.26.0/org/mockito/Mockito.html
翻譯版:
Mockito 中文文檔 ( 2.0.26 beta )
總結(jié)
以上是生活随笔為你收集整理的单元测试之带你搞懂Mockito使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Netty学习笔记(六)Pipeline
- 下一篇: 单元测试之更强大的powermock