溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

spring中如何使用Mockito解決Bean依賴(lài)樹(shù)問(wèn)題

發(fā)布時(shí)間:2021-09-23 11:40:09 來(lái)源:億速云 閱讀:138 作者:小新 欄目:編程語(yǔ)言

這篇文章主要介紹了spring中如何使用Mockito解決Bean依賴(lài)樹(shù)問(wèn)題,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

前提

基本概念 Junit初始化及存在的問(wèn)題

spring應(yīng)用在unit test時(shí),test是獨(dú)立運(yùn)行的,所以需要自行 init ApplicationContext,啟動(dòng) Ioc容器。

Junit要求:Test類(lèi)中涉及的所有Spring bean 注入成功才能完成applicationContext初始化,并啟動(dòng)IOC容器,否則無(wú)法執(zhí)行unit test。

ApplicationContext初始化的兩種方式 手動(dòng)注入(使用 @Bean或者 @Component 注入所需的類(lèi))編寫(xiě)@Configuration 類(lèi)(使用@ComponentScan 指定掃描beans) 兩種初始化方式存在的問(wèn)題

方式一:

所需的beans中,一個(gè)bean少注入了就會(huì)導(dǎo)致無(wú)法初始化上下文需要注入的bean太多時(shí),需要花費(fèi)大量的時(shí)間和精力,排查缺漏難度大

方式二:

顆粒度難以把控,隨著項(xiàng)目規(guī)模變大之后,可能導(dǎo)致bean導(dǎo)入過(guò)多,單元測(cè)試跑很久才能通過(guò)當(dāng)項(xiàng)目規(guī)模大了之后,bean之間的依賴(lài)往往是復(fù)雜的,掃描bean的方式可能出現(xiàn)一些不屬于自己模塊的未知問(wèn)題或者某些中間件在unitTest環(huán)境無(wú)法正常啟動(dòng),導(dǎo)致無(wú)法初始化上下文 什么是依賴(lài)樹(shù)?

在開(kāi)發(fā)應(yīng)用時(shí),往往會(huì)出現(xiàn)如上圖的 樹(shù)型依賴(lài) ,比如 serviceA 調(diào)用 serviceB,serviceB 又調(diào)用 serviceC 。

然而這只是一個(gè)簡(jiǎn)單的例子。真正的開(kāi)發(fā)中,往往一個(gè) service 會(huì)依賴(lài)多個(gè) service ,以及多個(gè) dao ,以此來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯。

而根據(jù)Junit要求,我們必須將樹(shù)的路徑經(jīng)過(guò)的所有節(jié)點(diǎn)(bean)都注入才能完成spring上下文初始化。這時(shí)如果bean之間的依賴(lài)耦合過(guò)大時(shí),就無(wú)法跳脫出兩種初始化方式帶來(lái)的問(wèn)題。

什么是Mockito?

在測(cè)試過(guò)程中,對(duì)于某些不容易構(gòu)造(如 HttpServletRequest 必須在Servlet 容器中才能構(gòu)造出來(lái))或者不容易獲取比較復(fù)雜的對(duì)象(如 JDBC 中的ResultSet 對(duì)象),用一個(gè)虛擬對(duì)象(Mock 對(duì)象)來(lái)創(chuàng)建以便測(cè)試的測(cè)試方法。

Mock 最大的功能是幫你把單元測(cè)試的耦合分解開(kāi),如果你的代碼對(duì)另一個(gè)類(lèi)或者接口有依賴(lài),它能夠幫你模擬這些依賴(lài),并幫你驗(yàn)證所調(diào)用的依賴(lài)的行為。

簡(jiǎn)單來(lái)說(shuō):就是虛擬一個(gè)mock對(duì)象,這個(gè)對(duì)象在單元測(cè)試時(shí)會(huì)“貍貓換太子”,將原有bean進(jìn)行替換,“騙過(guò)”spring初始化,成功啟動(dòng)ioc容器,以此規(guī)避常規(guī)初始化方式帶來(lái)的種種問(wèn)題。

開(kāi)發(fā)場(chǎng)景

結(jié)合本人在工作中遇見(jiàn)的問(wèn)題,當(dāng)時(shí)我所寫(xiě)的模塊進(jìn)行unitTest時(shí),就出現(xiàn)了依賴(lài)樹(shù)過(guò)于龐大的問(wèn)題。

首先,我采用了常規(guī)的手動(dòng)注入(方式一),導(dǎo)致注入了很久都沒(méi)注入完,無(wú)法執(zhí)行測(cè)試。后來(lái)覺(jué)得這方法在這種情況不可行。然后,我采用了編寫(xiě)@Configuration 類(lèi)(方式二),同樣也存在一些問(wèn)題。一些不屬于我負(fù)責(zé)模塊的bean也被注入,其中某些涉及TaskSchedule的bean無(wú)法被正確注入,導(dǎo)致無(wú)法執(zhí)行測(cè)試。此時(shí)一個(gè)個(gè)bean探索,解決問(wèn)題顯然不現(xiàn)實(shí)。最后,我采用Junit+Mockito結(jié)合的方式進(jìn)行單元測(cè)試。按照依賴(lài)樹(shù)大小進(jìn)行區(qū)分。 依賴(lài)樹(shù)小的直接使用常規(guī)的手動(dòng)注入(方式一),省事,同時(shí)保證大部分邏輯按照代碼正常運(yùn)行依賴(lài)樹(shù)大的使用Mockito,避免前文提到的兩種初始化方式導(dǎo)致的問(wèn)題

使用 1 導(dǎo)入maven依賴(lài)

首先導(dǎo)入mockito maven依賴(lài),版本請(qǐng)根據(jù)自己的spring版本選擇,否則會(huì)出現(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è)依賴(lài)已經(jīng)包含了mockito相關(guān)的jar包

spring-boot-starter-test可以使用 @MockBean 注解(mockito-core、mockito-all貌似不能)

@Mock和@MockBean的區(qū)別:

@Mock

@MockBean mock bean替換時(shí)機(jī) spring上下文初始化 完成之后 spring上下文初始化 執(zhí)行期間 能否“騙”過(guò)spring初始化否是 能否解決依賴(lài)樹(shù)否是 在沒(méi)注入所有所需的bean,無(wú)法完成spring上下文初始化時(shí),@Mock無(wú)法正常工作 @MockBean在初始化時(shí)就進(jìn)行替換,spring上下文初始化時(shí)檢測(cè)的bean為替換后的mock bean,而mock bean本身是無(wú)依賴(lài)任何其他bean的,自然能夠“騙”過(guò)spring上下文初始化階段,成功啟動(dòng)IOC容器 2 分析bean之間的依賴(lài)

使用一個(gè)簡(jiǎn)單的Demo進(jìn)行開(kāi)發(fā)場(chǎng)景的模擬,采用Junit+Mockito結(jié)合的方式進(jìn)行單元測(cè)試,根據(jù)依賴(lài)樹(shù)大小區(qū)分出是否需要mock

如圖,此處編寫(xiě)了一個(gè)ControllerA,ControllerA中依賴(lài)了2個(gè)bean:ServiceA,DaoA

分析過(guò)程: 關(guān)于 DaoA :由于Dao往往不會(huì)依賴(lài)其他的bean,所以此處可以使用常規(guī)的手動(dòng)注入(方式一)即可。方便快捷關(guān)于 ServiceA :由于serviceA依賴(lài)了serviceB(->DaoB)、serviceC(->DaoC),像這樣的嵌套依賴(lài)的bean就可以使用Mockito,來(lái)解決依賴(lài)樹(shù)問(wèn)題 3 編寫(xiě)Test類(lèi)

daoA使用@Bean注解注入即可

@Bean    public DaoA daoA(){      return new DaoAImpl();    }

1.serviceA首先使用@MockBean注解,將serviceA模擬為Mock Bean,它將在spring上下文初始化時(shí)就替換掉原有Bean

@MockBean  private ServiceA serviceA;

2.在test類(lèi)執(zhí)行前(@Before),使用Mockito API設(shè)置調(diào)用某個(gè)方法的返回值(你預(yù)期得到的返回結(jié)果),在Test類(lèi)中調(diào)用這個(gè)方法時(shí)就會(huì)返回所指定的值

@Before  public void init(){    MockitoAnnotations.initMocks(this);//只使用 @MockBean 時(shí)可省略這句    when(controllerA.serviceA_method()).thenReturn("666");  }

3.使用 @InjectMocks 通知依賴(lài)了serviceA的controllerA,在spring啟動(dòng)時(shí),對(duì)controllerA這個(gè)bean進(jìn)行相應(yīng)的后置處理

@Autowired  @InjectMocks  private ControllerA controller;

4.單元測(cè)試時(shí),就不會(huì)使用原有Bean的方法,而是使用Mock Bean及其已經(jīng)指定了返回值的方法

@Test  public void testDeepMock() {    String s = controllerA.serviceA_method();    System.out.println(s);  }

5.unitTest結(jié)果

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“spring中如何使用Mockito解決Bean依賴(lài)樹(shù)問(wèn)題”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI