Mockito使用總結
寫UT時,經常會遇到執行過程中調用的方法返回結果不可控的情況,為了專注在某個確定范圍內的開發自測,需要模擬這些方法和類的行為,Mockito提供了很好的解決方案。
使用Mockito可以很方便的設置、校驗方法或類的行為,但是前提是首先創建一個mock對象,才能基於Mockito進行操作。
創建一個mock對象
可以簡單的調用mock方法來創建一個mock對象:
List mockedList = Mockito.mock(List.class)
或者更簡單地,可以直接用@mock:
@Mock
List mockedList;
如果要使用注解,必須打開注解開關:
MockitoAnnotations.initMocks(this);
設置mock對象的行為
我們可以設置mock對象調用某個方法的返回值:
Mockito.when(mockedList.get(0)).thenReturn("val");
或者設置調用方法時拋出某個異常:
Mockito.when(mockedList.get(1)).thenThrow(new RuntimeException());
對於一個mock對象,沒有設置過的方法行為均返回null:
mockedList.get(999) // 將返回null
在實際使用中常常設置某個方法的返回值為另一個mock對象,在復雜的情況時可以以此來控制整個測試過程。
驗證mock對象的行為
1、驗證mock對象是否調用過某個方法:
Mockito.verify(mockedList).add("one"); // 是否執行過mockedList.add("one")
注意驗證的目標對象必須是mock的,否則會報錯。
2、驗證方法調用了多少次、是否從未調用過:
Mockito.verify(mockedList, Mockito.times(2)).add("one"); // 是否調用過兩次add("one")
Mockito.verify(mockedList, Mockito.never()).add("one"); // 是否從未調用過add("one")
3、驗證某個mock對象是否從未使用過:
Mockito.verifyZeroInteractions(mockedList);
4、驗證同一個mock對象執行不同方法的先后順序:
List singleMock = Mockito.mock(List.class);
singleMock.add("first");
singleMock.add("second");
InOrder inOrder = Mockito.inOrder(singleMock);
// 驗證調用順序
inOrder.verify(singleMock).add("first");
inOrder.verify(singleMock).add("second");
5、驗證不同mock對象執行不同方法的先后順序:
List firstMock = Mockito.mock(List.class);
List secondMock = Mockito.mock(List.class);
firstMock.add("first");
secondMock.add("second");
InOrder inOrder = Mockito.inOrder(firstMock,secondMock);
// 驗證調用順序
inOrder.verify(firstMock).add("first");
inOrder.verify(secondMock).add("second");
匹配器
匹配器可以代替一類參數:
1、驗證一類行為:
// 驗證是否執行過mockedList的get方法,參數為任意int數
Mockito.verify(mockedList).get(Mock.anyInt());
2、設置一類行為:
// 當mockedList調用get方法,參數為任意int數,返回值都是element
Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("element");
部分模擬
1、部分模擬的概念
前面提到,如果某個對象被mock,那么它將不再擁有原本的功能,全部的方法都被替換掉了,只會返回設置好的值,如果mock對象的某個方法沒有設置,那么就會返回null。當我們想要驗證某個對象的行為,同時又需要它一部分功能保持不變時,就要使用部分模擬:
// 使用部分模擬需要用spy方法,它必須是某個已經建立好的對象的封裝,不能用class對象為構造參數
List list = new LinkedList();
List mockedList = Mockito.spy(list);
后續我們可以正常調用它的方法,模擬它的部分行為,並驗證它的功能:
// 設置method1方法的行為
Mockito.when(mockedList.method1(Mockito.anyInt())).thenReturn("element");
// 調用method2將正常返回原本該返回的值,走正常的方法流程,與mock無關
mockedList.method2();
// 驗證對象的行為
Mockito.verify(mockedList).method1(Mock.anyInt());
2、不可模擬情況的替代方案
一旦對象是部分模擬的,在設置行為的過程中就可能發生異常,比如對於一個空集合mockedList,當執行下列語句時就會直接拋出越界異常:
Mockito.when(mockedList.get(0)).thenReturn("foo");
這種情況下應該用這種形式來模擬行為:
Mockito.doReturn("foo").when(mockedList).get(0);
3、使用mock來部分模擬
部分模擬也可以不用spy方法,使用mock一樣可以完成相同的功能:
List mockedList = Mockito.mock(List.class);
// 調用某個方法時返回真實值
Mockito.when(mockedList.someMethod()).thenCallRealMethod();
4、注解的使用
和mock一樣,spy方法也可以方便的用注解來代替,此時也要注意必須使用初始化后的對象,同時開啟注解:
@Spy
List list = new LinkedList();
5、使用部分模擬來完成對局部變量的替換
假設我們要測試這樣一段代碼:
Request request = new Request();
// 設置request的屬性
...
// 調用方法
Response response = serviceInvoker.invoke(SERVICE_NAME, request);
如果我們想模擬invoke方法的行為,很難准確的確定request這個參數,即使創建一個相同的對象,設置其行為:
// 創建request對象並設置其屬性
Mockito.when(serviceInvoker.invoke(SERVICE_NAME, request)).thenReturn("foo");
這樣也是行不通的,因為在待測試的代碼中,request是一個局部變量,它的地址和我們創建出來的對象地址是不同的,也就無法准確的進行模擬。
這種情況解決方案是在待測試代碼中將創建request的方法抽取出來:
Request request = getRequest();
// 調用方法
Response response = serviceInvoker.invoke(SERVICE_NAME, request);
然后在UT中,首先mock這個方法getRequest,使其返回值是我們控制好的request對象,然后再進行測試即可,這樣就能將我們預期的request對象放入方法中了:
// 創建request對象並設置其屬性
Request request = new Request();
...
// 模擬getRequest方法的行為
Mockito.when(instance.getRequest()).thenReturn(request);
// 完成我們一開始想要的行為定義
Mockito.when(serviceInvoker.invoke(SERVICE_NAME, request)).thenReturn("foo");
@InjectMocks
日常開發過程中,另外一個常用的注解是@InjectMocks,用它標注的類會自動裝配其中已經被@Mock和@Spy標注的字段:
// ModelDaoImpl類中有一個字段mediator, 這里自動裝配其中的mediator字段
@InjectMocks
ModelDaoImpl modelDao;
@Mock
Mediator mediator;
...{
// 這里調用的create是真實的方法,這里面如果有mediator調用某個方法,就可以通過自動裝配事先設置它的行為了
Mockito.when(mediator.get(0)).thenReturn("foo");
modelDao.create();
...
}
PowerMock
PowerMock可以來配合Mockito使用,模擬靜態方法的行為。
1、使用前的准備
需要在測試類上加注解:
@RunWith(PowerMockRunner.class) // 一個固定的啟動類
@PrepareForTest({Factory.class}) // 設置要模擬的靜態方法對應的類
2、設置靜態方法、或者新建對象的行為:
PowerMockito.mockStatic(Factory.class); // 用spyStatic可以部分模擬,在每次設置行為前都要執行該方法
PowerMockito.when(Factory.getLogger()).thenReturn(logger); // 設置Factory.getLogger()方法返回值
PowerMockito.doThrow(new RunTimeException()).when(Factory.class) // 執行返回值為void的方法時拋出異常
PowerMockito.whenNew(MyClass.class).withNoArguments().thenThrow(new IOEeception()); // 新建對象時拋出異常
3、驗證靜態方法、或者新建對象的行為:
// 檢查getLogger是否調用了2次
PowerMockito.verifyStatic(Mockito.times(2));
Factory.getLogger();
// 檢查getLogger是否調用了1次
PowerMockito.verifyStatic(); // default times is once
Factory.getLogger();
// 檢查getLogger是否從未被調用
PowerMockito.verifyStatic(Mockito.never());
Factory.getLogger();
// 是否新建過MyClass類的對象
PowerMockito.verifyNew(MyClass.class).withNoArguments();
4、私有方法的模擬與驗證:
// classUnderTest調用私有方法methodName,參數為parameter時,返回值為value
PowerMockito.doReturn(value).when(classUnderTest, "methodName", "parameter");
// classUnderTest是否調用兩次私有方法methodName,參數為parameter
PowerMockito.verifyPrivate(classUnderTest, Mockito.times(2)).invoke("methodName", "parameter");