SpringBoot單元測試(三)Mockito
描述
上一篇文章寫了幾個簡單MockMVC的測試類,可以簡單瞭解下MockMVC的用法。這篇文章主要是用來介紹Mockito的用法。它可以通過模擬物件來執行你需要的測試行為。
Mock介紹
Mock,從字面上就知道是模擬的意思。其實它就是建立一個虛擬的物件,然後在測試環境中代替真實的物件,以達到最終的測試目的。借用官方通用的說法:
- 驗證這個物件的某些方法的呼叫情況,呼叫了多少次,引數是什麼等等
- 指定這個物件的某些方法的行為,返回特定的值,或者是執行特定的動作
如果有開發前端的同行們都知道,Mock.js是前後端分離必不可少的一門技術。
Mockito介紹
Mockito是Mock框架裡應用最廣泛的,也是java的一個應用。市面上還有Powermock for Java、GoogleMock for C++、Mock Server、WireMock、MockMVC。
Mockito的使用
-
依賴
<!-- SpringBoot框架的測試包,自動會引入這些Mock相關的包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
<!-- 如果不是Spring framwork或純java框架,要引入Junit包,且mockito-core --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency>
-
建立Mock物件
-
使用靜態的mock()方法
-
使用@Mock註解
注意的一點是,如果使用@Mock註解方法,你必須要觸發@Mock註解物件的建立。
- 使用MockitoAnnotations.initMocks(this)方法觸發所有的@Mock註解物件的建立
/** * MockitoTest * * @author orjrs * @date 2018-12-31 11:15 */ public class MockitoTest { @Mock private GirlValidateService girlValidateService; @Before // @Test註解的方法執行前執行,觸發所有的@Mock註解物件的建立 public void setUp() { MockitoAnnotations.initMocks(this); } }
-
或者使用
@RunWith(MockitoJUnitRunner.class)
註解到類裡/** * MockitoTest * * @author orjrs * @date 2018-12-31 11:15 */ @RunWith(MockitoJUnitRunner.class) // 可以自動觸發所有的@Mock註解物件的建立 public class MockitoTest { @Mock private GirlValidateService girlValidateService; // @Before // @Test註解的方法執行前執行,觸發所有的@Mock註解物件的建立 // public void setUp() { // MockitoAnnotations.initMocks(this); // } }
-
或者使用MockitoRule
/** * MockitoTest * * @author orjrs * @date 2018-12-31 11:15 */ // @RunWith(MockitoJUnitRunner.class) // 可以自動觸發所有的@Mock註解物件的建立 public class MockitoTest { @Mock private GirlValidateService girlValidateService; @Rule // 可以自動觸發所有的@Mock註解物件的建立 public MockitoRule mockitoRule = MockitoJUnit.rule(); // @Before // @Test註解的方法執行前執行,觸發所有的@Mock註解物件的建立 // public void setUp() { // MockitoAnnotations.initMocks(this); // } }
-
-
完整的例子(GirlValidateServiceImpl和GirlServiceImpl後面再繼續貼了):
package com.orjrs.spring.test.service.impl;
import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.GirlService;
import com.orjrs.spring.test.service.GirlValidateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* GirlValidateServiceImpl
*
* @author orjrs
* @date 2018-12-31 11:40
*/
@Service("girlValidateService")
public class GirlValidateServiceImpl implements GirlValidateService {
private final GirlService girlService;
@Autowired
public GirlValidateServiceImpl(GirlService girlService) {
this.girlService = girlService;
}
@Override
public boolean validate(String name) {
if (StringUtils.isEmpty(name)) {
return false;
}
Girl girl = girlService.queryGirl(1L);
if (girl != null && name.equals(girl.getName())) {
return true;
}
return false;
}
}
package com.orjrs.spring.test.service.impl;
import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.GirlService;
import org.springframework.stereotype.Service;
/**
* GirlServiceImpl
*
* @author orjrs
* @date 2018-12-31 12:25
*/
@Service("girlService")
public class GirlServiceImpl implements GirlService {
@Override
public Girl queryGirl(Long id) {
Girl girl = new Girl();
girl.setId(1L);
girl.setName("張女士");
girl.setAge(23);
if (id.equals(girl.getId())) {
return girl;
}
return null;
}
}
/**
* GirlValidateServiceTest
*
* @author orjrs
* @date 2018-12-31 12:20
*/
public class GirlValidateServiceTest extends MockitoTest {// MockitoTest 參考上面其中任何一個
@Mock
private GirlService girlService;
@Test
public void validate() {
// 建立例項,mock物件作為建構函式的引數
GirlValidateService girlValidateService = new GirlValidateServiceImpl(girlService);
// 該方法體內呼叫GirlService的方法,此時@Mock出來的虛擬物件,是不會呼叫其實際方法
// girlService.queryGirl,所以返回的是null,導致判斷為false
boolean flag = girlValidateService.validate("張女士");
assertFalse(flag);
Mockito.verify(girlService).queryGirl(1L); // 驗證queryGirl呼叫是通過Mock物件的,這個方法的結果為null, girlService.queryGirl實際方法我已經寫死了:1L 張女士 23
}
}
- 配置模擬
-
when thenReturn 和 when thenThrow
簡單來說,當呼叫方法時,然後再返回自己設值或丟擲自己設定異常
/** * GirlValidateServiceTest * * @author orjrs * @date 2018-12-31 12:20 */ public class GirlValidateServiceTest extends MockitoTest { @Mock private GirlService girlService; @Test public void validate() { // 建立例項,mock物件作為建構函式的引數 GirlValidateService girlValidateService = new GirlValidateServiceImpl(girlService); // 2. 配置mocks的測試:如果呼叫這個方法,始終返回一個自定義的物件 Mockito.when(girlService.queryGirl(1L)).thenReturn(new Girl() {{ setId(0L); setName("張女士"); }}); // girlService.queryGirl實際方法我已經寫死了:1L 張女士 23 assertTrue(girlValidateService.validate("張女士")); // // 驗證校驗方法 } }
- doReturn when 和 doThrow when
它和when thenReturn 和when thenThrow很相似。它對於spy的用法來說是非常有用的。因為spy和mock的區別在於:前者會呼叫實際的方法,而mock不會。
請看下面關於spy的介紹,我會詳細說明這個方法的作用。
package com.orjrs.spring.test.service;
import com.orjrs.spring.test.model.Girl;
import com.orjrs.spring.test.service.impl.GirlValidateServiceImpl;
import com.orjrs.spring.test.unit.MockitoTest;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* GirlValidateServiceTest
*
* @author orjrs
* @date 2018-12-31 12:20
*/
public class GirlValidateServiceTest extends MockitoTest {
@Mock
private GirlService girlService;
@Spy // 如果不用@Spy註解,則可以Mockito.spy(spyGirlService)
private GirlService spyGirlService;
@Test
public void testSpyWrong() {
// mock 一個LinkedList
List<Girl> list = new LinkedList<>();
List<Girl> spy = Mockito.spy(list);
// 這個方法不會執行,因為spy的物件會去執行其實際方法,即spy.get(0)會去呼叫實際的方法,而list目前一個empty,則get(0)會異常
Mockito.when(spy.get(0)).thenReturn(new Girl() {{
setId(0L);
setName("李女士");
}});
// 上面一句會報錯,這裡不會執行
assertEquals("李女士", spy.get(0).getName());
}
@Test
public void testSpyCorrect() {
// mock 一個LinkedList
List<Girl> list = new LinkedList<>();
List<Girl> spy = Mockito.spy(list);
// 這個方法不會執行,因為spy的物件會去執行其實際方法,即spy.get(0)會去呼叫實際的方法,而list目前一個empty,則get(0)會異常
// 但doReturn when方法和when thenReturn不一樣,.when(spy)並不會報錯,呼叫get(0)它始終會返回一個結果
Mockito.doReturn(new Girl() {{
setId(0L);
setName("李女士");
}}).when(spy).get(0); // .when(spy).get(0);如果改成 when(spy.get(0)),一樣會報錯
// 會執行這行程式碼
assertEquals("李女士", spy.get(0).getName());
}
}
-
使用@InjectMocks依賴注入
@InjectMocks註解試圖將建構函式,方法或欄位通過基於型別來依賴注入。例如,假設你有下面的類。
注意:一定是基於型別,比如下面@InjectMocks // 建立一個例項,並注入@Mock的例項
private GirlValidateServiceImpl girlValidateServiceImpl;換成 GirlValidateServiceI介面就會報錯package com.orjrs.spring.test.service; import com.orjrs.spring.test.model.Girl; import com.orjrs.spring.test.service.impl.GirlValidateServiceImpl; import com.orjrs.spring.test.unit.MockitoTest; import org.junit.Test; import org.mockito.*; import java.util.LinkedList; import java.util.List; import static org.junit.Assert.*; /** * GirlValidateServiceTest * * @author orjrs * @date 2018-12-31 12:20 */ public class GirlValidateServiceTest extends MockitoTest { @Mock private GirlService girlService; @Spy private GirlService spyGirlService; @InjectMocks // 建立一個例項,並注入@Mock的例項 private GirlValidateServiceImpl girlValidateServiceImpl; @Test public void validate() { // 建立例項,mock物件作為建構函式的引數 //GirlValidateService girlValidateService = new //GirlValidateServiceImpl(girlService); // 可以直接呼叫imGirlValidateServiceg,不需要上面的初始化 imGirlValidateServiceg.validate("張女士"); } }
-
侷限性
Mockito不能模擬靜態方法和私有方法。