spring中使用Mockito解決Bean依賴樹問題方法
前提
本文不是針對Mockito的入門教學(xué) ,主要敘述如何簡單的使用Mockito解決Bean依賴樹問題,對于Mockito的學(xué)習(xí)請找其他的文章或者查閱官方文檔
基本概念 Junit初始化及存在的問題
spring應(yīng)用在unit test時(shí),test是獨(dú)立運(yùn)行的,所以需要自行 init ApplicationContext,啟動 Ioc容器。
Junit要求:Test類中涉及的所有Spring bean 注入成功才能完成applicationContext初始化,并啟動IOC容器,否則無法執(zhí)行unit test。
ApplicationContext初始化的兩種方式 手動注入(使用 @Bean或者 @Component 注入所需的類)編寫@Configuration 類(使用@ComponentScan 指定掃描beans) 兩種初始化方式存在的問題
方式一:
所需的beans中,一個(gè)bean少注入了就會導(dǎo)致無法初始化上下文需要注入的bean太多時(shí),需要花費(fèi)大量的時(shí)間和精力,排查缺漏難度大
方式二:
顆粒度難以把控,隨著項(xiàng)目規(guī)模變大之后,可能導(dǎo)致bean導(dǎo)入過多,單元測試跑很久才能通過當(dāng)項(xiàng)目規(guī)模大了之后,bean之間的依賴往往是復(fù)雜的,掃描bean的方式可能出現(xiàn)一些不屬于自己模塊的未知問題或者某些中間件在unitTest環(huán)境無法正常啟動,導(dǎo)致無法初始化上下文 什么是依賴樹?

在開發(fā)應(yīng)用時(shí),往往會出現(xiàn)如上圖的 樹型依賴 ,比如 serviceA 調(diào)用 serviceB,serviceB 又調(diào)用 serviceC 。
然而這只是一個(gè)簡單的例子。真正的開發(fā)中,往往一個(gè) service 會依賴多個(gè) service ,以及多個(gè) dao ,以此來實(shí)現(xiàn)業(yè)務(wù)邏輯。
而根據(jù)Junit要求,我們必須將樹的路徑經(jīng)過的所有節(jié)點(diǎn)(bean)都注入才能完成spring上下文初始化。這時(shí)如果bean之間的依賴耦合過大時(shí),就無法跳脫出兩種初始化方式帶來的問題。
什么是Mockito?
在測試過程中,對于某些不容易構(gòu)造(如 HttpServletRequest 必須在Servlet 容器中才能構(gòu)造出來)或者不容易獲取比較復(fù)雜的對象(如 JDBC 中的ResultSet 對象),用一個(gè)虛擬對象(Mock 對象)來創(chuàng)建以便測試的測試方法。
Mock 最大的功能是幫你把單元測試的耦合分解開,如果你的代碼對另一個(gè)類或者接口有依賴,它能夠幫你模擬這些依賴,并幫你驗(yàn)證所調(diào)用的依賴的行為。
簡單來說:就是虛擬一個(gè)mock對象,這個(gè)對象在單元測試時(shí)會“貍貓換太子”,將原有bean進(jìn)行替換,“騙過”spring初始化,成功啟動ioc容器,以此規(guī)避常規(guī)初始化方式帶來的種種問題。
開發(fā)場景
結(jié)合本人在工作中遇見的問題,當(dāng)時(shí)我所寫的模塊進(jìn)行unitTest時(shí),就出現(xiàn)了依賴樹過于龐大的問題。
首先,我采用了常規(guī)的手動注入(方式一),導(dǎo)致注入了很久都沒注入完,無法執(zhí)行測試。后來覺得這方法在這種情況不可行。然后,我采用了編寫@Configuration 類(方式二),同樣也存在一些問題。一些不屬于我負(fù)責(zé)模塊的bean也被注入,其中某些涉及TaskSchedule的bean無法被正確注入,導(dǎo)致無法執(zhí)行測試。此時(shí)一個(gè)個(gè)bean探索,解決問題顯然不現(xiàn)實(shí)。最后,我采用Junit+Mockito結(jié)合的方式進(jìn)行單元測試。按照依賴樹大小進(jìn)行區(qū)分。 依賴樹小的直接使用常規(guī)的手動注入(方式一),省事,同時(shí)保證大部分邏輯按照代碼正常運(yùn)行依賴樹大的使用Mockito,避免前文提到的兩種初始化方式導(dǎo)致的問題
使用 1 導(dǎo)入maven依賴
首先導(dǎo)入mockito maven依賴,版本請根據(jù)自己的spring版本選擇,否則會出現(xiàn)不兼容的情況。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
注意:
此處導(dǎo)入了spring-boot-starter-test是因?yàn)檫@個(gè)依賴已經(jīng)包含了mockito相關(guān)的jar包
spring-boot-starter-test可以使用 @MockBean 注解(mockito-core、mockito-all貌似不能)
@Mock和@MockBean的區(qū)別:
使用一個(gè)簡單的Demo進(jìn)行開發(fā)場景的模擬,采用Junit+Mockito結(jié)合的方式進(jìn)行單元測試,根據(jù)依賴樹大小區(qū)分出是否需要mock

如圖,此處編寫了一個(gè)ControllerA,ControllerA中依賴了2個(gè)bean:ServiceA,DaoA
分析過程: 關(guān)于 DaoA :由于Dao往往不會依賴其他的bean,所以此處可以使用常規(guī)的手動注入(方式一)即可。方便快捷關(guān)于 ServiceA :由于serviceA依賴了serviceB(->DaoB)、serviceC(->DaoC),像這樣的嵌套依賴的bean就可以使用Mockito,來解決依賴樹問題 3 編寫Test類

daoA使用@Bean注解注入即可
@Bean
public DaoA daoA(){
return new DaoAImpl();
}
1.serviceA首先使用@MockBean注解,將serviceA模擬為Mock Bean,它將在spring上下文初始化時(shí)就替換掉原有Bean
@MockBean private ServiceA serviceA;
2.在test類執(zhí)行前(@Before),使用Mockito API設(shè)置調(diào)用某個(gè)方法的返回值(你預(yù)期得到的返回結(jié)果),在Test類中調(diào)用這個(gè)方法時(shí)就會返回所指定的值
@Before
public void init(){
MockitoAnnotations.initMocks(this);//只使用 @MockBean 時(shí)可省略這句
when(controllerA.serviceA_method()).thenReturn("666");
}
3.使用 @InjectMocks 通知依賴了serviceA的controllerA,在spring啟動時(shí),對controllerA這個(gè)bean進(jìn)行相應(yīng)的后置處理
@Autowired @InjectMocks private ControllerA controller;
4.單元測試時(shí),就不會使用原有Bean的方法,而是使用Mock Bean及其已經(jīng)指定了返回值的方法
@Test
public void testDeepMock() {
String s = controllerA.serviceA_method();
System.out.println(s);
}
5.unitTest結(jié)果


以上就是本次介紹的全部相關(guān)知識點(diǎn),感謝大家的學(xué)習(xí)和對腳本之家的支持。
相關(guān)文章
一文詳解SpringBoot如何優(yōu)雅地實(shí)現(xiàn)異步調(diào)用
SpringBoot想必大家都用過,但是大家平時(shí)使用發(fā)布的接口大都是同步的,那么你知道如何優(yōu)雅的實(shí)現(xiàn)異步呢?這篇文章就來和大家詳細(xì)聊聊2023-03-03
Java實(shí)現(xiàn)從數(shù)據(jù)庫導(dǎo)出大量數(shù)據(jù)記錄并保存到文件的方法
這篇文章主要介紹了Java實(shí)現(xiàn)從數(shù)據(jù)庫導(dǎo)出大量數(shù)據(jù)記錄并保存到文件的方法,涉及Java針對數(shù)據(jù)庫的讀取及文件寫入等操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:三角數(shù)字
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)及算法實(shí)例:三角數(shù)字,本文直接給出實(shí)現(xiàn)代碼,代碼中包含詳細(xì)注釋,需要的朋友可以參考下2015-06-06
redis setIfAbsent和setnx的區(qū)別與使用說明
這篇文章主要介紹了redis setIfAbsent和setnx的區(qū)別與使用,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring中如何動態(tài)注入Bean實(shí)例教程
這篇文章主要給大家介紹了關(guān)于Spring中如何動態(tài)注入Bean的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
SpringBoot 下的 Static 文件夾打包成前端資源的示例代碼
這篇文章主要介紹了SpringBoot 下的 Static 文件夾如何打包成前端資源,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-06-06
Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門教程
這篇文章主要介紹了Java commons io包實(shí)現(xiàn)多線程同步圖片下載入門,commons io: 是針對開發(fā)IO流功能的工具類庫,其中包含了許多可調(diào)用的函數(shù),感興趣的朋友跟隨小編一起看看吧2021-04-04
Java UrlRewriter偽靜態(tài)技術(shù)運(yùn)用深入分析
通常我們?yōu)榱烁玫木徑夥?wù)器壓力,和增強(qiáng)搜索引擎的友好面,都將文章內(nèi)容生成靜態(tài)頁面,這就產(chǎn)生了偽靜態(tài)技術(shù),也就是我們常說的Url Rewriter重寫技術(shù)2012-12-12

