Java Springboot 后端使用Mockito庫進(jìn)行單元測試流程分析
1 為什么要使用mock進(jìn)行單元測試
如果使用SpringbootTest進(jìn)行單元測試,那么將啟動spring服務(wù),生成容器和bean并加載所有外部資源或依賴,當(dāng)工程量很大時啟動非常費時,顯然為了一個小小的單元測試啟動整個項目有一些大動干戈。
此外,有時部分外部依賴無法獲?。ㄈ鐢?shù)據(jù)庫連接、緩存或接口獲取有問題),而單元測試旨在測試某個類具體的方法,測試的是該方法的功能邏輯,不應(yīng)關(guān)注其依賴的對象,以防測試時各級方法互相調(diào)用太深(調(diào)用的方法應(yīng)該在它自己的單元測試中進(jìn)行測試),相當(dāng)于只關(guān)注測試方法的寬度而不在意它的深度。
因此,對于要測試的方法中使用到的外部類或方法,可以考慮使用模擬對象來模擬它們的調(diào)用并自定義好返回的值,這樣能夠在不執(zhí)行真實調(diào)用方法的同時讓單元測試正常進(jìn)行,這就是mock的原理。使用mock可以很好的將要測試的方法和它的依賴分隔開,降低了模塊間的耦合,非常適合外部資源較難構(gòu)造或方法調(diào)用太深的場景。
2 使用mock的注意點
- 通過mock進(jìn)行單元測試時并未啟動spring,那么不會加載容器并生成bean,就無法通過自動注入的方式獲取依賴,需要使用mock注解
- 如果測試的相關(guān)功能與spring有關(guān)(比如AOP),那么還是需要使用SpringbootTest
- 啟用mock時可以看成啟動了普通的java項目,在普通項目中能運行的代碼在mock中也能運行
- 單元測試時并非所有調(diào)用的外部方法或使用到的外部類都需要mock,如實體類可以直接new,工具類方法可以直接運行(不存在外部依賴的工具類,比如用來進(jìn)行數(shù)據(jù)格式處理的工具類,通常調(diào)用其靜態(tài)方法),不需要使用mock
3 mock使用流程
3.1 測試前配置
使用mock首先需要在pom文件中導(dǎo)入相關(guān)依賴,然后可以使用@Mock或Mockito庫的mock方法來生成模擬對象,如果使用@Mock則需要先添加以下注解或代碼:
- 在測試類前添加
@RunWith(MockitoJUnitRunner.class)或@ExtendWith(MockitoExtension.class) - 在測試類內(nèi)添加如下方法:
//測試前準(zhǔn)備,可以在這里進(jìn)行一些基礎(chǔ)配置
@BeforeEach
void setUp(){
MockitoAnnotations.openMocks(this); //開啟mock,搭配@Mock使用
}3.2 注入待測試類并模擬其中使用的變量
若需要測試的類為UserService,那么在它的測試類UserServiceTest中需要使用注解@InjectMocks注入UserService的實例,該注解的作用是自動創(chuàng)建并注入一個類的實例,同時將標(biāo)記為@Mock的依賴注入到該實例中,注意@InjectMocks只能注入class。
@InjectMocks UserService userService;
3.2.1 模擬成員變量
待測試類的成員變量需要使用@Mock注解注入,無法通過Mockito.mock()方法生成模擬對象,mock()方法生成的成員模擬對象在執(zhí)行時會報空指針錯誤,并未真正生成模擬對象。
若UserService中有成員變量UserMapper,那么使用如下代碼注入:
@Mock UserMapper
簡單來說就是將UserService里面的所有成員變量直接復(fù)制到Test里,然后將@Autowired改成@Mock即可。
3.2.2 模擬靜態(tài)對象
當(dāng)代碼中需要調(diào)用某個類的靜態(tài)方法時,需要使用mockStatic()方法生成一個模擬靜態(tài)對象,如被測方法中若存在如下代碼:
DictUtil.getIns("A_END"); //DictUtil類中存在一些外部依賴,因此需要模擬那么首先需要生成DictUtil的模擬靜態(tài)對象:
mockStatic(DictUtil.class); //上下兩種寫法都可以 MockedStatic<DictUtil> dictUtilMockedStatic = mockStatic(DictUtil.class);
3.2.3 模擬普通變量
對于代碼中生成的一些中間變量,而在后期又需要使用這些變量進(jìn)行方法調(diào)用時,需要先使用mock將中間變量模擬出,如被測方法中若存在代碼:
UserCache.getInstance().getName("12345");那么需要先模擬出UserCache的一個實例(打樁時不能對連續(xù)調(diào)用的方法同時打樁,需先生成中間變量),此時可以使用@Mock或者mock方法:
// 1. 當(dāng)成測試類成員變量注入 @Mock UserCache userCache; // 2. 在測試方法中直接模擬 UserCache userCache = Mockito.mock(UserCache.class);
3.3 打樁模擬方法調(diào)用行為
3.3.1 非靜態(tài)方法打樁
打樁即對調(diào)用的方法模擬傳參并設(shè)置返回值,參數(shù)個數(shù)和返回值需要與原方法對應(yīng)。打樁之后不會真正去調(diào)用,會忽略真實方法的執(zhí)行結(jié)果,一般使用Mockito.when().thenReturn()進(jìn)行打樁。
若被測類方法中存在代碼:
User user = UserMapper.selectById("0000");那么測試方法中的打樁可以是:
//實體類不需要mock,可以直接生成,然后使用setter方法賦值
User user = new User();
// 1. 固定了打樁參數(shù)為“0000”,源代碼調(diào)用中參數(shù)為非“0000”時打樁失敗
Mockito.when(UserMapper.selectById("0000")).thenReturn(user)
// 2. 為了代碼的健壯性,通常使用Mockito.anyxxx()方法來接收參數(shù),根據(jù)xxx的不同來指定不同的類型,只要是該類型的任意參數(shù)都可以打樁
Mockito.when(UserMapper.selectById(Mockito.anyString())).thenReturn(user)3.3.2 靜態(tài)方法打樁
以原代碼UserCache.getInstance().getName("12345");為例,靜態(tài)方法打樁有兩種寫法:
1) 普通寫法
UserCache userCache = Mockito.mock(UserCache.class);
mockStatic(UserCache.class);
Mockito.when(UserCache.getInstance()).thenReturn(userCache);
Mockito.when(userCache.getName(Mockito.anyString())).thenReturn("TestName");2) lambda寫法-無參靜態(tài)方法調(diào)用
UserCache userCache = Mockito.mock(UserCache.class);
MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class);
//1. 當(dāng)靜態(tài)方法無參時,可以使用::進(jìn)行方法調(diào)用
userCacheMockedStatic.when(UserCache::getInstance).thenReturn(userCache);
//2. 也可以使用lambda寫法,使用.進(jìn)行調(diào)用
userCacheMockedStatic.when(() -> UserCache.getInstance()).thenReturn(userCache);
//3. 不能使用如下寫法:
//userCacheMockedStatic.when(UserCache.getInstance()).thenReturn(userCache);
Mockito.when(userCache.getName(Mockito.anyString())).thenReturn("TestName");3) lambda寫法-有參靜態(tài)方法調(diào)用
假如上述getInstance方法有一個int參數(shù),那么可以通過1)中的普通寫法進(jìn)行打樁,或者使用2)-2的方式進(jìn)行打樁,然后使用Mockito.anyString()傳參,但是不能使用2)-1或2)-3的寫法。
3.3.3 Maven test靜態(tài)模擬報錯問題
當(dāng)單獨運行一個Test時沒有問題,但是當(dāng)集體執(zhí)行Test時會出現(xiàn)如下報錯:
‘static mocking is already registered in the current thread To create a new mock, the existing static mock registration must be deregistered’
這是因為有兩個以上的測試方法都使用了同一個靜態(tài)mock對象,如兩個Test中都有MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class),而靜態(tài)mock對象不允許公用,因此需要在每個靜態(tài)mock對象執(zhí)行后對其進(jìn)行關(guān)閉,下一個方法才能繼續(xù)使用它,故需要在每個Test末尾中寫:userCacheMockedStatic.close()。
由于MockedStatic<T>的父接口繼承了AutoCloseable接口,因此可以使用 try-resources語句實現(xiàn)資源的自動關(guān)閉:
try(
// mockStatic(UserCache.class); //java8不支持這種寫法
MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class);
// ……其他靜態(tài)模擬對象聲明
){
//其他測試代碼
}也可以選擇用try-catch-finally在finally語句里手動關(guān)閉:
try(
MockedStatic<UserCache> userCacheMockedStatic = mockStatic(UserCache.class);
// ……其他測試代碼
)finally{
userCacheMockedStatic.close();
}3.4 斷言判斷測試結(jié)果
要檢測測試的結(jié)果,需要使用斷言Assertions進(jìn)行判斷,根據(jù)實際測試要求來:
User user = UserService.getById("123");
User expectUsr = new User("123");
Assertions.assertEquals(expectUsr ,user)://判斷測試結(jié)果user與期望的對象是否一致
Assertions.assertNotNull(user)://判斷測試結(jié)果user是否非空到此這篇關(guān)于Java Springboot 后端使用Mockito庫進(jìn)行單元測試流程的文章就介紹到這了,更多相關(guān)Springboot Mockito單元測試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Cloud Gateway 默認(rèn)的filter功能和執(zhí)行順序介紹
這篇文章主要介紹了Spring Cloud Gateway 默認(rèn)的filter功能和執(zhí)行順序,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
SpringBoot2.3新特性優(yōu)雅停機(jī)詳解
這篇文章主要介紹了SpringBoot2.3新特性優(yōu)雅停機(jī)詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

