在真實的測試當中,并不能所有的邏輯都可以自己控制,因此有了mock測試。今天就結合場景來講一下怎么做mock測試。
適合對象:初次嘗試集成和使用mockito進行單元測試的開發同學

Mock框架的集成
這里選擇的是Mockito + PowerMockito。為什么會集成PowerMockito,是因為有個想要mock的方法是static方法。這個需要PowerMockito,假如都只是普通類,就可以不用了。
集成關鍵點如下
1、版本對應:這兩個mockito的版本是有一個對應關系,假如不對應,會出現類找不到的情況。比如 ClassNotFound org/mockito/mockitoframework 。而網上也已經有對應關系如下鏈接
https://github.com/powermock/powermock/wiki/Mockito#supported-versions
這是我的集成版本
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.28.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
2、注解方式集成:因為使用spring boot的項目,所以考慮怎么用注解方式集成。最后我的注解方案如下所示
@RunWith(PowerMockRunner.class)
@PrepareForTest({JdbcClient.class})
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@PowerMockIgnore({"JAVAx.net.ssl.*","javax.management.*", "javax.security.*", "javax.crypto.*"})
這幾個,都有自己的用處,分別說下:
- @RunWith(PowerMockRunner.class) :假如要使用powermock,那就需要配置這個,不然powermock無法使用
- @PrepareForTest({JdbcClient.class}) :這里的列表,設定的是需要static mock 的類,我的測試場景中,需要mock JdbcClient的static query 方法
- @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class) : 一開始,只配置了前兩個,然后發現從spring中inject注入的service都為空了。原來要需要使用這個注解,這樣就和spring test 組合上了
- @PowerMockIgnore({"javax.net.ssl.*","javax.management.*","javax.security.*","javax.crypto.*"}) :PowerMock大概是mock的太多了,所以假如沒有這行注釋,也是各種報錯。 ClassCastException: class sun.security.provider.ConfigFile 這種。
TIPS:這些注解可以寫到一個abstract TestClass 上,后面的測試類繼承這個就方便了
Mock場景舉例
好,正式開始mock。首先來講,網上那些例子,好多都太簡單了,不能當做實際場景。比如mock ClassA.method1,然后直接驗證method1的結果。這種只能驗證集成的對不對。還是結合真實場景比較好。
場景一:修改外部服務調用,比如調用支付寶支付,或者發個短信什么的,也可以是數據庫查詢
有時候,我們不希望真的去調用外部(比如配置太復雜,比如收費,比如想模擬錯誤結果)。或者想自己控制數據庫查詢結果(或者遇到了我這邊只有正式環境才有某個庫的情況)。那這時候,就需要使用Mock Service來解決。
先擼代碼,再分析。
這個場景講解mock的常規使用手法
以及在spring注入service情況下,如何處理mock
public class MockitoTest extends BaseTest {
@Autowired
DemoService demoService;
@Mock
RemoteService remoteService;
@Test
public void testHack() throws Exception {
demoService.setRemoteService(remoteService);
String result = "fail";
Mockito.when(remoteService.sendRequest(any())).thenReturn(result);
String callResult = demoService.callRemote("something");
assertEquals("fail", callResult);
Mockito.verify(remoteService).sendRequest(any());
}
}
這個場景感覺用的挺多的。demoService是想要測試的功能,其中用到了remoteService.sendRequest的結果。而又不想實際調用remoteService。這時候就可以先把remoteService.sendRequest給mock掉,給出自己設定的返回結果。
這時候要注意的一個點是:remoteService并不歸屬springContext管理,所以run test 以后,會發現,這個mock毫無作用。debug之下,可以看到注入和mock的remoteService并不是一個實例。
那如何使remoteSerivce變成mockService,這里有兩個思路。
一、就是上面的方案,用mockSerivce去替換demoSerivce里的remoteService。
demoService.setRemoteService(remoteService);
二、替換springContext里面的remoteSerice,這就需要使用@MockBean 這個注解。 然后,所有注入的remoteService,都是Mock生成的service
@MockBean
RemoteService remoteService;
但是據說MockBean有副作用,會多次重啟Spring context。可能也會污染上下文。暫時沒有去嘗試研究。
場景二:修改靜態類方法,比如一些單例的方法。
在我的場景里,是自定義了一個單例JdbcClient,client保存連接池,然后發起請求。
這個是使用PowerMockito,因為只有他能mock static方法
先來代碼
public class MockitoTest extends BaseTest {
@Autowired
DemoService demoService;
@Test
public void testHack() throws Exception {
String result = "fail";
PowerMockito.mockStatic(JdbcClient.class);
Mockito.when(JdbcClient.sendRequest(any())).thenReturn(result);
String callResult = demoService.callRemote("something");
assertEquals("fail", callResult);
}
}
這里的主要用法就是 PowerMockito.mockStatic 這個。但是要結合之前的兩個注解,@RunWith(PowerMockRunner.class) @PrepareForTest({JdbcClient.class}) 這個使用才有效。
就寫這兩個場景吧。后續用到比較好的場景再補充,也歡迎大家提供更多使用場景一起學習學習。