中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

網(wǎng)站的原型怎么做百度搜索競(jìng)價(jià)排名

網(wǎng)站的原型怎么做,百度搜索競(jìng)價(jià)排名,如何在門(mén)戶(hù)網(wǎng)站做搜索引擎,庫(kù)爾勒網(wǎng)站建設(shè)價(jià)格文章目錄 單元測(cè)試Mockito1. 入門(mén)1.1 什么是Mockito1.2 優(yōu)勢(shì)1.3 原理 2. 使用2.0 環(huán)境準(zhǔn)備2.1 Mock1) Mock對(duì)象創(chuàng)建2) 配置Mock對(duì)象的行為(打樁)3) 驗(yàn)證方法調(diào)用4) 參數(shù)匹配5) 靜態(tài)方法 2.2 常用注解1) Mock2) BeforeEach 與 BeforeAfter3) InjectMocks4) Spy5) Captor6) RunWi…

文章目錄

  • 單元測(cè)試Mockito
  • 1. 入門(mén)
    • 1.1 什么是Mockito
    • 1.2 優(yōu)勢(shì)
    • 1.3 原理
  • 2. 使用
    • 2.0 環(huán)境準(zhǔn)備
    • 2.1 Mock
      • 1) Mock對(duì)象創(chuàng)建
      • 2) 配置Mock對(duì)象的行為(打樁)
      • 3) 驗(yàn)證方法調(diào)用
      • 4) 參數(shù)匹配
      • 5) 靜態(tài)方法
    • 2.2 常用注解
      • 1) @Mock
      • 2) @BeforeEach 與 @BeforeAfter
      • 3) @InjectMocks
      • 4) @Spy
      • 5) @Captor
      • 6) @RunWith和@ExtendWith
        • @RunWith
        • @ExtendWith
    • 2.3 常見(jiàn)區(qū)別
      • Mock對(duì)象和Spy對(duì)象區(qū)別
  • 3. Springboot 使用
    • 3.1 數(shù)據(jù)準(zhǔn)備
      • 創(chuàng)建sql
      • 引入依賴(lài)
      • 添加application.yml
      • 編寫(xiě)實(shí)體類(lèi)
      • 編寫(xiě)Service層
      • 編寫(xiě)controller
    • 3.2 測(cè)試
      • 1) 創(chuàng)建Mock或者Spy對(duì)象
        • 方法一
        • 方法二
        • 方法三
      • 2) 參數(shù)匹配
      • 3) 打樁
      • 4) 多次打樁
      • 5) 實(shí)戰(zhàn)
    • 3.3 Springboot測(cè)試注解
      • @MockBean
      • @SpyBean

單元測(cè)試Mockito

名稱(chēng)鏈接備注
mockito英文文檔Mockito (Mockito 5.12.0 API) (javadoc.io)
mockito中文文檔Mockito 中文文檔 ( 2.0.26 beta ) - 《Mockito 框架中文文檔》 - 極客文檔 (geekdaxue.co)
視頻教學(xué)鏈接https://www.bilibili.com/video/BV1P14y1k7Hi

1. 入門(mén)

1.1 什么是Mockito

Mockito是Java生態(tài)系統(tǒng)中最受歡迎的單元測(cè)試模擬框架之一,以其簡(jiǎn)潔易用的API和強(qiáng)大的模擬能力贏得了廣大開(kāi)發(fā)者的青睞。Mockito允許我們?cè)诓粚?shí)際依賴(lài)外部資源的情況下對(duì)代碼進(jìn)行徹底且高效的單元測(cè)試,極大地提升了測(cè)試覆蓋率和代碼質(zhì)量。

1.2 優(yōu)勢(shì)

Mockito是一種模擬框架,其核心概念是在測(cè)試過(guò)程中創(chuàng)建并使用“Mock對(duì)象”。Mock對(duì)象是對(duì)實(shí)際對(duì)象的一種模擬,它繼承或?qū)崿F(xiàn)了被測(cè)試類(lèi)所依賴(lài)的接口或類(lèi),但其行為可以根據(jù)測(cè)試需求自由定制??刂破湓跍y(cè)試環(huán)境下的行為,從而將注意力聚焦于類(lèi)本身的邏輯驗(yàn)證上。

  • 隔離度高:通過(guò)模擬依賴(lài),減少測(cè)試間的耦合,確保單元測(cè)試真正只關(guān)注被測(cè)試單元的內(nèi)部邏輯。
  • 易于使用:API設(shè)計(jì)直觀簡(jiǎn)潔,降低了編寫(xiě)和閱讀測(cè)試用例的難度。
  • 詳盡的驗(yàn)證:能夠準(zhǔn)確跟蹤和驗(yàn)證被測(cè)試對(duì)象與其依賴(lài)之間的交互行為。
  • 靈活性強(qiáng):支持多種定制模擬行為,無(wú)論是簡(jiǎn)單的返回值還是復(fù)雜的回調(diào)機(jī)制。
  • 有利于TDD實(shí)踐:與測(cè)試驅(qū)動(dòng)開(kāi)發(fā)方法論緊密契合,鼓勵(lì)寫(xiě)出更易于測(cè)試的代碼。

1.3 原理

Mockito 的底層原理是使用 cglib 動(dòng)態(tài)生成一個(gè) 代理類(lèi)對(duì)象,因此,mock 出來(lái)的對(duì)象其實(shí)質(zhì)就是一個(gè) 代理,該代理在 沒(méi)有配置/指定行為 的情況下,默認(rèn)返回空值

2. 使用

2.0 環(huán)境準(zhǔn)備

創(chuàng)建一個(gè)普通的maven項(xiàng)目。添加依賴(lài)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.ucarinc.framework</groupId><artifactId>demo1</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>demo1</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.13</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>2.0.13</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.10.2</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.32</version></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>5.11.0</version><scope>test</scope></dependency></dependencies></project>

2.1 Mock

1) Mock對(duì)象創(chuàng)建

使用Mockito.mock()方法創(chuàng)建接口或抽象類(lèi)的Mock對(duì)象。下面是它的方法接口

public static <T> T mock(Class<T> classToMock)
  • classToMock:待 mock 對(duì)象的 class 類(lèi)。
  • 返回 mock 出來(lái)的類(lèi)

實(shí)例:使用 mock 方法 mock 一個(gè)類(lèi)

import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;public class MyTest {@Testpublic void myTest() {/* 創(chuàng)建 Mock 對(duì)象 */List list = mock(List.class);/* 設(shè)置預(yù)期,當(dāng)調(diào)用 get(0) 方法時(shí)返回 "111" */when(list.get(0)).thenReturn("111");Assert.assertEquals("asd", 1, 1);/* 設(shè)置后返回期望的結(jié)果 */System.out.println(list.get(0));/* 沒(méi)有設(shè)置則返回 null */System.out.println(list.get(1));/* 對(duì) Mock 對(duì)象設(shè)置無(wú)效 */list.add("12");list.add("123");/* 返回之前設(shè)置的結(jié)果 */System.out.println(list.get(0));/* 返回 null */System.out.println(list.get(1));/* size 大小為 0 */System.out.println(list.size());/* 驗(yàn)證操作,驗(yàn)證 get(0) 調(diào)用了 2 次 */verify(list, times(2)).get(0);/* 驗(yàn)證返回結(jié)果 */String ret = (String)list.get(0);Assert.assertEquals(ret, "111");}
}  

總結(jié)

junit4junit5
方法一@RunWith(MockitojUnitRunner.class)+@Mock等注解@ExtendWith(MockitoExtension.class)+@Mock等注解
方法二Mockito.mock(X.class)MockitoAnnotations.open等靜態(tài)方法Mockito.mock(X.class)MockitoAnnotations.open等靜態(tài)方法
方法三Mocks(this)+@Mock等注解Mocks(this)+@Mock等注解

2) 配置Mock對(duì)象的行為(打樁)

使用whenthenReturn方法配置Mock對(duì)象的行為:

打樁可以理解為mock對(duì)象規(guī)定一行的行為,使其按照我們的要求來(lái)執(zhí)行具體的操作。在Mockito中,常用的打樁方法為

方法含義
when().thenReturn()Mock 對(duì)象在觸發(fā)指定行為后返回指定值
when().thenThrow()Mock 對(duì)象在觸發(fā)指定行為后拋出指定異常
when().doCallRealMethod()Mock 對(duì)象在觸發(fā)指定行為后調(diào)用真實(shí)的方法

thenReturn() 代碼示例

    public void test02(){// 模擬random對(duì)象,這個(gè)對(duì)象是假的Random random = Mockito.mock(Random.class);// 當(dāng)調(diào)用了random對(duì)象時(shí),返回100這個(gè)值Mockito.when(random.nextInt()).thenReturn(100);// 驗(yàn)證,應(yīng)該是對(duì)的。有人會(huì)問(wèn),random.nextInt()不是獲取隨機(jī)值嗎?// 現(xiàn)在這個(gè)random對(duì)象是假的Assertions.assertEquals(100, random.nextInt());}

完整的另一個(gè)demo

package com.ucarinc.framework;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;public class App5Test {private final Logger log= LoggerFactory.getLogger(App5Test.class);public static class MockitoTestController{public int add(int a, int b){System.out.println("測(cè)試了a+b  a="+a+",b="+b);return a+b;}}@Testvoid testAdd() {MockitoTestController mockitoTestController = mock(MockitoTestController.class);// 設(shè)置mock對(duì)象的行為(打樁),當(dāng)調(diào)用add(1, 2)時(shí)返回4when(mockitoTestController.add(1, 2)).thenReturn(4);// 調(diào)用mock對(duì)象的方法,返回為4int result = mockitoTestController.add(1, 2);log.info("mockitoTestController.add result={}",result);// 斷言驗(yàn)證:調(diào)用add(1, 2)方法返回值是否為4Assertions.assertEquals(mockitoTestController.add(1, 2),4);// 驗(yàn)證:確保add方法(1, 2)被調(diào)用了一次verify(mockitoTestController,times(2)).add(1, 2);}}

你還可以配置方法拋出異常:

 /*** 測(cè)試當(dāng)調(diào)用add方法時(shí)拋出RuntimeException異常的情況。* 該測(cè)試函數(shù)不接受參數(shù),也沒(méi)有返回值。*/@Testvoid testAddException() {TestController mockitoTestController = Mockito.mock(TestController.class);// 設(shè)置mock對(duì)象,在調(diào)用mockitoTestController的add方法時(shí)拋出RuntimeException異常when(mockitoTestController.add(1, 2)).thenThrow(new RuntimeException("add error"));// 驗(yàn)證是否拋出了RuntimeException異常Assertions.assertThrows(RuntimeException.class, () -> mockitoTestController.add(1, 2));}public static class TestController{public int add(int a, int b){System.out.println("測(cè)試了a+b="+a+",b="+b);return a+b;}}

有種特殊情況,就是void返回值打樁

package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.service.UserService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;import java.util.List;import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.verify;@ExtendWith(MockitoExtension.class)
public class Test4 {@MockList<String> mockList;@Testpublic void test1(){doNothing().when(mockList).clear();mockList.clear();verify(mockList).clear();}
}

3) 驗(yàn)證方法調(diào)用

Mock對(duì)象進(jìn)行行為驗(yàn)證和結(jié)果斷言。驗(yàn)證是校驗(yàn)對(duì)象是否發(fā)生過(guò)某些行為,Mockito 中驗(yàn)證的方法是:verify。

常見(jiàn)的驗(yàn)證方法包括:

  • verify(mock).methodCall():驗(yàn)證方法被調(diào)用
  • verify(mock, times(n)).methodCall():驗(yàn)證方法被調(diào)用n次
  • verify(mock, never()).methodCall():驗(yàn)證方法從未被調(diào)用

驗(yàn)證交換:Verify 配合 time() 方法,可以校驗(yàn)?zāi)承┎僮靼l(fā)生的次數(shù)。
注意:當(dāng)使用 mock 對(duì)象時(shí),如果不對(duì)其行為進(jìn)行定義,則 mock 對(duì)象方法的返回值為返回類(lèi)型的默認(rèn)值。

package com.ucarinc.framework;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;import java.util.Random;import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;public class AppTest{@Testpublic void test01() {// 使用Mockito模擬一個(gè)Random對(duì)象Random random = Mockito.mock(Random.class);// 調(diào)用nextInt()方法,輸出隨機(jī)數(shù),因random 行為為進(jìn)行打樁,故輸出默認(rèn)值0(random.nextInt() 返回的是int類(lèi)型)System.out.println("第一次:"+random.nextInt());// 驗(yàn)證random.nextInt()這個(gè)方法是否只調(diào)用了一次verify(random).nextInt();// 指定當(dāng)調(diào)用nextInt()時(shí),始終返回1Mockito.when(random.nextInt()).thenReturn(1);System.out.println("第二次:"+random.nextInt()); // 再次調(diào)用nextInt(),輸出應(yīng)為1// 斷言nextInt()方法返回值是否為1Assertions.assertEquals(1,random.nextInt());// 驗(yàn)證nextInt()方法是否被調(diào)用了兩次verify(random, times(3)).nextInt();}
}

4) 參數(shù)匹配

Mockito提供了多種參數(shù)匹配器(Matchers)用于更靈活的驗(yàn)證和配置行為:

import static org.mockito.ArgumentMatchers.*;when(mockRepository.findById(anyInt())).thenReturn(Optional.of(user));
verify(mockRepository).findById(eq(1));

常見(jiàn)的匹配器包括:

  • any():匹配任何參數(shù)
  • anyInt():匹配任何整數(shù)參數(shù)
  • eq(value):匹配特定值
  • isNull():匹配null值
  • notNull():匹配非null值

5) 靜態(tài)方法

添加依賴(lài)

    <dependency><groupId>org.mockito</groupId><artifactId>mockito-inline</artifactId><version>5.2.0</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.14.0</version></dependency>

如果jdk版本低的話(huà),版本可以低一點(diǎn).

使用 mockStatic() 方法來(lái) mock靜態(tài)方法的所屬類(lèi),此方法返回一個(gè)具有作用域的模擬對(duì)象。

    @Testpublic void testJoinWith() {// 使用 Mockito 框架模擬 StringUtils 類(lèi)的靜態(tài)方法MockedStatic<StringUtils> stringUtilsMockedStatic = Mockito.mockStatic(StringUtils.class);// 創(chuàng)建一個(gè)字符串列表,作為 joinWith 方法的輸入?yún)?shù)List<String> stringList = Arrays.asList("a", "b", "c");// 配置模擬行為,當(dāng)調(diào)用 StringUtils.joinWith(",", stringList) 時(shí),返回 "a,b,c"stringUtilsMockedStatic.when(() -> StringUtils.joinWith(",", stringList)).thenReturn("a,b,c");// 斷言驗(yàn)證模擬行為是否正確,即 joinWith 方法返回的字符串是否與預(yù)期的 "a,b,c" 相等Assertions.assertTrue(StringUtils.joinWith(",", stringList).equals("a,b,c"));}

但是如果你寫(xiě)成下面這樣子的話(huà),會(huì)發(fā)送報(bào)錯(cuò)

package com.ucarinc.framework;import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;import java.util.Arrays;
import java.util.List;class Demo2ApplicationTests {@Testpublic void testJoinWith() {// 使用 Mockito 框架模擬 StringUtils 類(lèi)的靜態(tài)方法MockedStatic<StringUtils> stringUtilsMockedStatic = Mockito.mockStatic(StringUtils.class);// 創(chuàng)建一個(gè)字符串列表,作為 joinWith 方法的輸入?yún)?shù)List<String> stringList = Arrays.asList("a", "b", "c");// 配置模擬行為,當(dāng)調(diào)用 StringUtils.joinWith(",", stringList) 時(shí),返回 "a,b,c"stringUtilsMockedStatic.when(() -> StringUtils.joinWith(",", stringList)).thenReturn("a,b,c");// 斷言驗(yàn)證模擬行為是否正確,即 joinWith 方法返回的字符串是否與預(yù)期的 "a,b,c" 相等Assertions.assertTrue(StringUtils.joinWith(",", stringList).equals("a,b,c"));}/*** 測(cè)試StringUtils類(lèi)中的join方法。* 該測(cè)試使用Mockito框架來(lái)模擬靜態(tài)方法的行為,驗(yàn)證join方法是否按照預(yù)期工作。* */@Testpublic void testJoin() {// 使用Mockito模擬StringUtils類(lèi)的靜態(tài)方法MockedStatic<StringUtils> stringUtilsMockedStatic = Mockito.mockStatic(StringUtils.class);// 創(chuàng)建一個(gè)字符串列表作為join方法的輸入List<String> stringList = Arrays.asList("a", "b", "c");// 配置模擬行為,當(dāng)調(diào)用StringUtils.join(",", stringList)時(shí),返回字符串"a,b,c"stringUtilsMockedStatic.when(() -> StringUtils.join(",", stringList)).thenReturn("a,b,c");// 斷言驗(yàn)證模擬行為是否正確,即 join 方法返回的字符串是否與預(yù)期的 "a,b,c" 相等Assertions.assertTrue(StringUtils.join(",", stringList).equals("a,b,c"));}}

然后執(zhí)行整個(gè)測(cè)試類(lèi)后會(huì)報(bào)錯(cuò):,就會(huì)報(bào)錯(cuò)

image-20240712094211482

原因是因?yàn)?mockStatic() 方法是將當(dāng)前需要 mock 的類(lèi)注冊(cè)到本地線程上(ThreadLocal),而這個(gè)注冊(cè)在一次 mock 使用完之后是不會(huì)消失的,需要我們手動(dòng)的去銷(xiāo)毀。如過(guò)沒(méi)有銷(xiāo)毀,再次 mock 這個(gè)類(lèi)的時(shí)候 Mockito 將會(huì)提示我們 :”當(dāng)前對(duì)象 mock 的對(duì)象已經(jīng)在線程中注冊(cè)了,請(qǐng)先撤銷(xiāo)注冊(cè)后再試“。這樣做的目的也是為了保證模擬出來(lái)的對(duì)象之間是相互隔離的,保證同時(shí)和連續(xù)的測(cè)試不會(huì)收到上下文的影響。

2.2 常用注解

1) @Mock

快速 mock 的方法,使用 @mock 注解。

mock 注解需要搭配 MockitoAnnotations.openMocks(testClass) 方法一起使用。

package com.ucarinc.framework;import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;import java.util.Arrays;
import java.util.List;
import java.util.Random;import static org.mockito.Mockito.*;public class App2Test {@Mockprivate Random random;@BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);}/*** 測(cè)試Mockito框架的使用,模擬Random類(lèi)的nextInt方法。* 該測(cè)試函數(shù)沒(méi)有參數(shù)和返回值,主要用于演示Mockito的基本用法。*/@Testpublic void test02() {// 調(diào)用nextInt()方法,輸出隨機(jī)數(shù),因random 行為為進(jìn)行打樁,故輸出默認(rèn)值0(random.nextInt() 返回的是int類(lèi)型)System.out.println("第一次:"+random.nextInt());// 指定當(dāng)調(diào)用nextInt()時(shí),始終返回1Mockito.when(random.nextInt()).thenReturn(1);System.out.println("第二次:"+random.nextInt()); // 再次調(diào)用nextInt(),輸出應(yīng)為1// 斷言nextInt()方法返回值是否為1Assertions.assertEquals(1,random.nextInt());// 驗(yàn)證nextInt()方法是否被調(diào)用了兩次verify(random, times(3)).nextInt();}}

2) @BeforeEach 與 @BeforeAfter

package com.ucarinc.framework;import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.Random;import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;public class RandomTest02 {private final Logger log= LoggerFactory.getLogger(RandomTest02.class);@Mockprivate Random random;@BeforeEachvoid setUp() {log.info("==============測(cè)試前準(zhǔn)備===============");MockitoAnnotations.openMocks(this);}/*** 測(cè)試Mockito框架的使用,模擬Random類(lèi)的nextInt方法。* 該測(cè)試函數(shù)沒(méi)有參數(shù)和返回值,主要用于演示Mockito的基本用法。*/@Testpublic void test02() {// 調(diào)用nextInt()方法,輸出隨機(jī)數(shù),因random 行為為進(jìn)行打樁,故輸出默認(rèn)值0(random.nextInt() 返回的是int類(lèi)型)System.out.println("第一次:"+random.nextInt());// 指定當(dāng)調(diào)用nextInt()時(shí),始終返回1Mockito.when(random.nextInt()).thenReturn(1);System.out.println("第二次:"+random.nextInt()); // 再次調(diào)用nextInt(),輸出應(yīng)為1// 斷言nextInt()方法返回值是否為1Assertions.assertEquals(1,random.nextInt());// 驗(yàn)證nextInt()方法是否被調(diào)用了兩次verify(random, times(3)).nextInt();}@AfterEachvoid tearDown() {log.info("==============測(cè)試后結(jié)果===============");}}

image-20240712095557244

3) @InjectMocks

@InjectMocks用于將模擬對(duì)象注入到被測(cè)試類(lèi)中的相應(yīng)字段。通過(guò)該注解可以自動(dòng)將模擬對(duì)象注入到被測(cè)試類(lèi)中標(biāo)記為@InjectMocks的字段中,可以理解為使用@Mock創(chuàng)建出來(lái)的對(duì)象注入到@InjectMocks創(chuàng)建的對(duì)象中,這樣被測(cè)試類(lèi)就可以使用模擬對(duì)象作為其依賴(lài)了。

package com.ucarinc.framework;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.ArrayList;
import java.util.List;import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;public class App6Test {@MockAClass aClass;@InjectMocksBClass bClass;@BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);}@Testvoid testAdd() {// 當(dāng)調(diào)用a方法時(shí),直接返回1000。a是模擬的when(aClass.add()).thenReturn(1000);Assertions.assertEquals(1003, bClass.add(1,2));}public static class AClass{public AClass(){}public int add(){System.out.println("AClass.add");return 1;}}@Data@AllArgsConstructor@NoArgsConstructorpublic static class BClass  {private AClass aClass;public int add(int a, int b) {// 調(diào)用a方法int add = aClass.add();System.out.println("測(cè)試了a+b  a=" + a + ",b=" + b + ",add=" + add);return a + b + add;}}}

通常配合@Mock注解一起使用,一般用作service層。然后把mock的mapper層注入其中

@InjectMocks
private UserService userService;@MockBean
private UserMapper userMapper;

4) @Spy

spy() 方法與 mock() 方法不同的是

  1. spy 的對(duì)象會(huì)走真實(shí)的方法,而 mock 對(duì)象不會(huì)
  2. spy() 方法的參數(shù)是對(duì)象實(shí)例,mock 的參數(shù)是 class

首先,我們使用mock方法。做一個(gè)測(cè)試

package com.ucarinc.framework;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;import java.util.Random;import static org.mockito.Mockito.*;public class App3Test {public static class MockitoTestController{public int add(int a, int b){System.out.println("測(cè)試了a+b  a="+a+",b="+b);return a+b;}}@Testpublic void test01() {MockitoTestController   mockitoTestController =new MockitoTestController();// 調(diào)用實(shí)際的 mockitoTestController 對(duì)象的 add 方法,并驗(yàn)證結(jié)果是否為預(yù)期值int result = mockitoTestController.add(1, 2);Assertions.assertEquals(3, result);// 使用 Mockito 創(chuàng)建 mockitoTest 的 mock 對(duì)象,并對(duì)它調(diào)用 add 方法,然后驗(yàn)證結(jié)果MockitoTestController mockitoTest = Mockito.mock(MockitoTestController.class);int result1 = mockitoTest.add(1, 2);Assertions.assertEquals(3, result1);}}

返回的結(jié)果

第二個(gè) Assertions 斷言失敗,因?yàn)闆](méi)有給 mockitoTest 對(duì)象打樁,因此返回默認(rèn)值

image-20240712100357578

使用@Spy()注解示例。引入依賴(lài)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.ucarinc.framework</groupId><artifactId>demo1</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>demo1</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>2.0.13</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>2.0.13</version></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.10.2</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.32</version></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>5.11.0</version><scope>test</scope></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-inline</artifactId><version>5.2.0</version><scope>test</scope></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.14.0</version></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>5.11.0</version><scope>test</scope></dependency></dependencies></project>

代碼測(cè)試

package com.ucarinc.framework;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;@ExtendWith(MockitoExtension.class)
public class App4Test {private final Logger log= LoggerFactory.getLogger(App4Test.class);public static class MockitoTestController{public int add(int a, int b){System.out.println("測(cè)試了a+b  a="+a+",b="+b);return a+b;}}@Spyprivate MockitoTestController mockitoTestController;@BeforeEachvoid setUp() {}/*** 測(cè)試add方法* 該方法模擬調(diào)用mockitoTestController的add方法,傳入?yún)?shù)1和2,期望返回值為3。* 首先,通過(guò)when語(yǔ)句設(shè)置mockitoTestController的add方法返回值為3;* 然后,使用assertThat斷言驗(yàn)證調(diào)用add方法(1, 2)實(shí)際返回值確實(shí)為3;* 最后,通過(guò)verify語(yǔ)句確認(rèn)mockitoTestController的add方法確實(shí)被調(diào)用了一次,并傳入了參數(shù)1和2。*/@Testvoid testAdd() {// 設(shè)置mock對(duì)象的行為(打樁),當(dāng)調(diào)用add(1, 2)時(shí)返回4when(mockitoTestController.add(1, 2)).thenReturn(4);// 調(diào)用mock對(duì)象的方法,返回為4int result = mockitoTestController.add(1, 2);log.info("mockitoTestController.add result={}",result);// 斷言驗(yàn)證:調(diào)用add(1, 2)方法返回值是否為4Assertions.assertEquals(mockitoTestController.add(1, 2),4);// 驗(yàn)證:確保add方法(1, 2)被調(diào)用了一次verify(mockitoTestController,times(2)).add(1, 2);}}

5) @Captor

接下來(lái),我們來(lái)看看如何使用@Captor注解來(lái)創(chuàng)建ArgumentCaptor實(shí)例。

在以下示例中,我們將在不使用@Captor注釋的情況下創(chuàng)建ArgumentCaptor:

@Test
public void whenNotUseCaptorAnnotation_thenCorrect() {List mockList = Mockito.mock(List.class);ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);mockList.add("one");Mockito.verify(mockList).add(arg.capture());assertEquals("one", arg.getValue());
}

使用@Captor來(lái)創(chuàng)建一個(gè)ArgumentCaptor實(shí)例:

    @MockList<String> mockedList;@CaptorArgumentCaptor<String> argCaptor;@BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);}@Testpublic void whenUseCaptorAnnotation_thenTheSame() {mockedList.add("one");verify(mockedList).add(argCaptor.capture());assertEquals("one", argCaptor.getValue());}

6) @RunWith和@ExtendWith

測(cè)試類(lèi)上使用 @RunWith(SpringRunner.class) 注解(使用的是 JUnit 4)
測(cè)試類(lèi)上使用 @ExtendWith(SpringExtension.class)注解(使用的是 JUnit 5)

SpringBoot2.4.x之后,改為默認(rèn)僅集成JUnit5,干掉了兼容JUnit4

@RunWith
  • @RunWith就是一個(gè)運(yùn)行器
  • @RunWith(JUnit4.class)就是指用JUnit4來(lái)運(yùn)行
  • @RunWith(SpringJUnit4ClassRunner.class),讓測(cè)試運(yùn)行于Spring測(cè)試環(huán)境,以便在測(cè)試開(kāi)始的時(shí)候自動(dòng)創(chuàng)建Spring的應(yīng)用上下文
@RunWith(SpringRunner.class) //14.版本之前用的是SpringJUnit4ClassRunner.class
@SpringBootTest(classes = Application.class) //1.4版本之前用的是//@SpringApplicationConfiguration(classes = Application.class)
public class SystemInfoServiceImplTest {@Autowiredprivate ISystemInfoService systemInfoservice;@Testpublic void add() throws Exception {}@Testpublic void findAll() throws Exception {}}
@ExtendWith

@ExtendWith 具體Demo展示如下:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;// 定義一個(gè)自定義的JUnit擴(kuò)展,用于在測(cè)試開(kāi)始前輸出日志
class CustomExtension implements BeforeTestExecutionCallback {@Overridepublic void beforeTestExecution(ExtensionContext context) {System.out.println("Before Test Execution");}
}// 使用@ExtendWith注解加載自定義擴(kuò)展
@ExtendWith(CustomExtension.class)
public class test {@Testvoid test1() {System.out.println("Test 1");Assertions.assertTrue(true);}@Testvoid test2() {System.out.println("Test 2");Assertions.assertEquals(2, 1 + 1);}
}

Mockito通常與JUnit結(jié)合使用,特別是JUnit 5,利用@ExtendWith(MockitoExtension.class)簡(jiǎn)化Mock對(duì)象的初始化

啟動(dòng)類(lèi)加上@ExtendWith(MockitoExtension.class),會(huì)自動(dòng)處理@Mock@Spy@InjectMocks等注解

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;@ExtendWith(MockitoExtension.class)
public class UserServiceTest {// 測(cè)試代碼
}

2.3 常見(jiàn)區(qū)別

Mock對(duì)象和Spy對(duì)象區(qū)別

方法插樁方法不插樁作用對(duì)象最佳實(shí)踐
mock對(duì)象執(zhí)行插樁邏輯返回mock對(duì)象的默認(rèn)值類(lèi)、接口被測(cè)試類(lèi)或其依賴(lài)
spy對(duì)象執(zhí)行插樁邏輯調(diào)用真實(shí)方法類(lèi)、接口被測(cè)試類(lèi)

3. Springboot 使用

首先看下完整的pom結(jié)構(gòu)

image-20240712113509616

3.1 數(shù)據(jù)準(zhǔn)備

創(chuàng)建sql

create database if not exists mockito;
use mockito;
DROP TABLE IF EXISTS `user`;CREATE TABLE `user`
(id    BIGINT      NOT NULL COMMENT '主鍵ID',name  VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',age   INT         NULL DEFAULT NULL COMMENT '年齡',email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',PRIMARY KEY (id)
);
INSERT INTO `user` (id, name, age, email)
VALUES (1, 'Jone', 18, 'test1@baomidou.com'),(2, 'Jack', 20, 'test2@baomidou.com'),(3, 'Tom', 28, 'test3@baomidou.com'),(4, 'Sandy', 21, 'test4@baomidou.com'),(5, 'Billie', 24, 'test5@baomidou.com');

引入依賴(lài)

創(chuàng)建springboot 項(xiàng)目。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.1</version><relativePath/> <!-- lookup parent from repository --></parent><modelVersion>4.0.0</modelVersion><groupId>com.lkcoffee.framework</groupId><artifactId>demo2</artifactId><version>0.0.1-SNAPSHOT</version><name>demo2</name><description>demo2</description><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>3.3.1</spring-boot.version></properties><dependencies><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.7</version></dependency><!--       springbbot配置--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope><version>8.3.0</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.28</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.lkcoffee.framework.demo2.Demo2Application</mainClass><skip>true</skip></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>

添加application.yml

server:port: 8080spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8servlet:multipart:max-file-size: 1024MBmax-request-size: 1024MBapplication:name: demo2datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/mockito?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=trueusername: rootpassword: rootmybatis-plus:global-config:db-config:logic-delete-field: isDeletelogic-delete-value: 1logic-not-delete-value: 0mapper-locations: classpath*:mapper/**/*Mapper.xmlconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpllogging:file:name: test.loglevel:root: INFOorg:springframework: DEBUGexample:springboottest: DEBUG

在Springboot 啟動(dòng)類(lèi)中添加 @MapperScan 注解,掃描 Mapper 文件夾:

package com.lkcoffee.framework.demo2;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("com.lkcoffee.framework.demo2.mapper")
@SpringBootApplication
public class Demo2Application {public static void main(String[] args) {SpringApplication.run(Demo2Application.class, args);}}

編寫(xiě)實(shí)體類(lèi)

import lombok.Data;@Data
public class User {private Long id;private String name;private Integer age;private String email;
}

編寫(xiě) Mapper 接口類(lèi) UserMapper.java

import org.springframework.stereotype.Repository;@Repository
public interface UserMapper extends BaseMapper<User> {}

編寫(xiě)Service層

package com.lkcoffee.framework.demo2.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.lkcoffee.framework.demo2.domain.User;import java.util.List;/*** @Desciption: 用戶(hù)服務(wù)層* @Author: feixiang.li* @date: 2024-07-11 19:51**/
public interface UserService  extends IService<User> {/*** 查詢(xún)所有用戶(hù)信息* @return 所有用戶(hù)信息*/List<User> queryAll();/*** 根據(jù)用戶(hù)id查詢(xún)* @param id 用戶(hù)id* @return 用戶(hù)信息*/User queryById(Long id);/*** 添加用戶(hù)id* @param user 用戶(hù)信息* @return 操作結(jié)果*/Boolean addUser(User user);/*** 根據(jù)用戶(hù)id修改用戶(hù)信息* @param user* @return*/Integer updateUser(User user);
}

實(shí)現(xiàn)Service層

package com.lkcoffee.framework.demo2.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lkcoffee.framework.demo2.domain.User;
import com.lkcoffee.framework.demo2.mapper.UserMapper;
import com.lkcoffee.framework.demo2.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Objects;/*** @Desciption: 用戶(hù)操作類(lèi)* @Author: feixiang.li* @date: 2024-07-12 10:39**/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {@Overridepublic List<User> queryAll() {log.info("被真實(shí)調(diào)用了, 執(zhí)行了 查詢(xún)所有用戶(hù)信息");return list();}@Overridepublic User queryById(Long id) {log.info("被真實(shí)調(diào)用了, 根據(jù)用戶(hù)id:{} 查詢(xún)用戶(hù)",id);return getById(id);}@Transactional(rollbackFor = Exception.class)@Overridepublic Boolean addUser(User user) {log.info("被真實(shí)調(diào)用了, 添加用戶(hù)信息:{}",user);if(Objects.nonNull(user.getId())){throw new RuntimeException("被真實(shí)調(diào)用了,新增用戶(hù),id應(yīng)該為空");}if(Objects.isNull(user.getAge()) || user.getAge() < 0 || user.getAge() > 100){throw new RuntimeException("被真實(shí)調(diào)用了,請(qǐng)?zhí)顚?xiě)正確的年齡");}if(StringUtils.isBlank(user.getName())){throw new RuntimeException("被真實(shí)調(diào)用了,對(duì)不起,姓名不能為空");}return save(user);}@Transactional(rollbackFor = Exception.class)@Overridepublic Integer updateUser(User user) {System.out.println("執(zhí)行了真實(shí)的更新用戶(hù)方法");int result= getBaseMapper().updateById(user);System.out.println("update user result:"+result);return result;}}

編寫(xiě)controller

package com.lkcoffee.framework.demo2.controller;import com.lkcoffee.framework.demo2.domain.User;
import com.lkcoffee.framework.demo2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;
import java.util.Objects;
import java.util.Optional;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 10:45**/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic List<User> queryAll(){return userService.queryAll();}@GetMapping("/{id}")public User queryById(@PathVariable Long id){if(Objects.isNull(id)){return new User();}return userService.queryById(id);}@PostMappingpublic String save(@RequestBody User user){if(Objects.isNull(user)){return "對(duì)象為空";}userService.save(user);return "success";}
}

啟動(dòng)項(xiàng)目: 訪問(wèn)下面

http://localhost:8080/user

返回一下結(jié)果,說(shuō)明項(xiàng)目啟動(dòng)成功;

image-20240712105244873

3.2 測(cè)試

1) 創(chuàng)建Mock或者Spy對(duì)象

junit4junit5
方法一@RunWith(MockitojUnitRunner.class)+@Mock等注解@ExtendWith(MockitoExtension.class)+@Mock等注解
方法二Mockito.mock(X.class)MockitoAnnotations.open等靜態(tài)方法Mockito.mock(X.class)MockitoAnnotations.open等靜態(tài)方法
方法三Mocks(this)+@Mock等注解Mocks(this)+@Mock等注解
方法一
package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;@ExtendWith(MockitoExtension .class)
public class Test1 {@Mockprivate UserService mockUserService;@Spyprivate UserService spyUserService;@Testpublic void test1(){// 判斷某個(gè)對(duì)象是不是mock對(duì)象System.out.println("Mockito.mockingDetails(mockUserService).isMock(): "+ Mockito.mockingDetails(mockUserService).isMock());System.out.println("Mockito.mockingDetails(spyUserService).isSpy(): "+ Mockito.mockingDetails(spyUserService).isSpy());}
}
方法二
package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;public class Test2 {@Mockprivate UserService mockUserService;@Spyprivate UserService spyUserService;@BeforeEachpublic void init() {mockUserService=Mockito.mock(UserService.class);spyUserService=Mockito.spy(UserService.class);}@Testpublic void test1(){// 判斷某個(gè)對(duì)象是不是mock對(duì)象System.out.println("Mockito.mockingDetails(mockUserService).isMock(): "+ Mockito.mockingDetails(mockUserService).isMock());System.out.println("Mockito.mockingDetails(spyUserService).isSpy(): "+ Mockito.mockingDetails(spyUserService).isSpy());}
}
方法三
package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.service.UserService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;public class Test3 {@Mockprivate UserService mockUserService;@Spyprivate UserService spyUserService;@BeforeEachpublic void init() {MockitoAnnotations.openMocks(this);}@Testpublic void test1(){// 判斷某個(gè)對(duì)象是不是mock對(duì)象System.out.println("Mockito.mockingDetails(mockUserService).isMock(): "+ Mockito.mockingDetails(mockUserService).isMock());System.out.println("Mockito.mockingDetails(spyUserService).isSpy(): "+ Mockito.mockingDetails(spyUserService).isSpy());}
}

MockitoAnnotations.initMocks(this)和MockitoAnnotations.openMocks(this)

這兩個(gè)效果一樣,只是在juit5中initMocks被拋棄了

MockitoAnnotations.initMocks(this)方法并不會(huì)產(chǎn)生代理類(lèi),它主要是用于初始化Mockito注解。在測(cè)試中,我們通常使用@Mock、@Spy、@InjectMocks等注解來(lái)創(chuàng)建Mock對(duì)象,并使用Mockito.when、Mockito.verify等方法來(lái)模擬對(duì)象的行為和驗(yàn)證方法調(diào)用。

但是,如果我們不調(diào)用MockitoAnnotations.initMocks(this)方法,這些Mock對(duì)象就無(wú)法被正確初始化,從而導(dǎo)致測(cè)試失敗。因此,我們通常在@Before注解方法中調(diào)用這個(gè)方法,以確保所有的Mock對(duì)象都已經(jīng)被正確初始化。

在具體實(shí)現(xiàn)中,MockitoAnnotations.initMocks(this)方法會(huì)掃描測(cè)試類(lèi)中所有的@Mock、@Spy、@InjectMocks注解,并根據(jù)注解中的類(lèi)型和名稱(chēng)來(lái)創(chuàng)建對(duì)應(yīng)的Mock對(duì)象,并將這些對(duì)象注入到測(cè)試類(lèi)中。這樣,在測(cè)試過(guò)程中就可以使用這些Mock對(duì)象來(lái)模擬外部依賴(lài),從而實(shí)現(xiàn)單元測(cè)試的獨(dú)立性和可重復(fù)性。

2) 參數(shù)匹配

package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.domain.User;
import com.lkcoffee.framework.demo2.service.UserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;/*** 參數(shù)匹配:通過(guò)方法簽名(參數(shù))來(lái)制定哪些方法調(diào)用需要被處理*/
@ExtendWith(MockitoExtension.class)
public class ParamMatcherTest {private final Logger log = LoggerFactory.getLogger(ParamMatcherTest.class);@Mockprivate UserService mockUserService;@Spyprivate UserService spyUserService;@Testpublic void test2() {/*** 這里返回值是null. Mock對(duì)象不會(huì)調(diào)用真實(shí)方法*/User user = new User();user.setId(1L);user.setName("fly");doReturn(99).when(mockUserService).updateUser(user);int result1 = mockUserService.updateUser(user);log.info("用戶(hù)1修改對(duì)象返回值:{}", result1);User user2 = new User();user.setId(2L);user.setName("name2");int result2 = mockUserService.updateUser(user2);log.info("用戶(hù)2修改對(duì)象返回值:{}", result2);// 現(xiàn)在我想任意用戶(hù)都返回99doReturn(99).when(mockUserService).updateUser(any());result1 = mockUserService.updateUser(user);result2 = mockUserService.updateUser(user2);log.info("用戶(hù)1修改對(duì)象返回值:{}", result1);log.info("用戶(hù)2修改對(duì)象返回值:{}", result2);}@Testpublic void test1() {/*** 這里返回值是null. Mock對(duì)象不會(huì)調(diào)用真實(shí)方法。如果不進(jìn)行插樁的話(huà)*/User user = mockUserService.queryById(1L);log.info("user:{}", user);}
}

3) 打樁

package com.lkcoffee.framework.demo2;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;@ExtendWith(MockitoExtension.class)
public class App4Test {private final Logger log= LoggerFactory.getLogger(App4Test.class);public static class MockitoTestController{public int add(int a, int b){System.out.println("調(diào)用了真實(shí)方法 測(cè)試了a+b  a="+a+",b="+b);return a+b;}}@Spyprivate MockitoTestController spyMockitoTestController;@BeforeEachvoid setUp() {}/*** 測(cè)試add方法* 該方法模擬調(diào)用mockitoTestController的add方法,傳入?yún)?shù)1和2,期望返回值為3。* 首先,通過(guò)when語(yǔ)句設(shè)置mockitoTestController的add方法返回值為3;* 然后,使用assertThat斷言驗(yàn)證調(diào)用add方法(1, 2)實(shí)際返回值確實(shí)為3;* 最后,通過(guò)verify語(yǔ)句確認(rèn)mockitoTestController的add方法確實(shí)被調(diào)用了一次,并傳入了參數(shù)1和2。*/@Testvoid testAdd() {// 設(shè)置mock對(duì)象的行為(打樁),當(dāng)調(diào)用add(1, 2)時(shí)返回4// 雖然使用了when ,但是已經(jīng)調(diào)用了真實(shí)方法when(spyMockitoTestController.add(1, 2)).thenReturn(4);// 調(diào)用mock對(duì)象的方法,返回為4int result = spyMockitoTestController.add(1, 2);log.info("mockitoTestController.add result={}",result);// 斷言驗(yàn)證:調(diào)用add(1, 2)方法返回值是否為4Assertions.assertEquals(spyMockitoTestController.add(1, 2),4);// 驗(yàn)證:確保add方法(1, 2)被調(diào)用了一次verify(spyMockitoTestController,times(2)).add(1, 2);/*** spy對(duì)象在沒(méi)有攝性時(shí)是謂用真實(shí)方法的,號(hào)加en中會(huì)導(dǎo)致先技行一次方法,達(dá)不打樁的目的* 需使用 doXxx().when(obj).someNethod()*/doReturn(99).when(spyMockitoTestController).add(anyInt(),anyInt());int result2 = spyMockitoTestController.add(1, 2);log.info("spyMockitoTestController.add result={}",result2);}}

如果使用springboot的話(huà),低端用法,沒(méi)有使用@SpringbootTest@SpyBean注解

package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.domain.User;
import com.lkcoffee.framework.demo2.mapper.UserMapper;
import com.lkcoffee.framework.demo2.service.UserService;
import com.lkcoffee.framework.demo2.service.impl.UserServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;@ExtendWith(MockitoExtension.class)
public class Test5 {@Mockprivate UserMapper userMapper;@Mockprivate UserServiceImpl mockUserService;@InjectMocks@Spyprivate UserServiceImpl spyUserService;@Testpublic void test1() {// 這一步是為了解決mybatisplus的問(wèn)題。手動(dòng)把mapper注入進(jìn)去。// 如果使用了Autowired 的 Resource ,就不需要這一步了doReturn(userMapper).when(spyUserService).getBaseMapper();User user = new User();user.setId(1L);user.setName("name1");when(userMapper.updateById(any(User.class))).thenReturn(-1);when(mockUserService.updateUser(user)).thenReturn(99);int result1 = mockUserService.updateUser(user);System.out.println("result1 = " + result1);when(spyUserService.updateUser(user)).thenReturn(99);int result2 = spyUserService.updateUser(user);System.out.println("result2 = " + result2);/*** spy對(duì)象在沒(méi)有攝性時(shí)是謂用真實(shí)方法的,號(hào)加en中會(huì)導(dǎo)致先技行一次方法,達(dá)不och的目的* 需使用 doXxx().when(obj).someNethod()*/doReturn(100).when(spyUserService).updateUser(any());int result3 = spyUserService.updateUser(user);System.out.println("result3 = " + result3);}
}

執(zhí)行結(jié)果對(duì)象

result1 = 99
執(zhí)行了真實(shí)的更新用戶(hù)方法
update user result:-1
result2 = 99
result3 = 100

4) 多次打樁

package com.lkcoffee.framework.demo2;/*** @Desciption:* @Author: feixiang.li* @date: 2024-07-12 14:38**/import com.lkcoffee.framework.demo2.domain.User;
import com.lkcoffee.framework.demo2.mapper.UserMapper;
import com.lkcoffee.framework.demo2.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;import java.util.List;import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;@ExtendWith(MockitoExtension.class)
public class Test6 {@Mockprivate List<Integer> mockList;@Testpublic void test1() {//第1次調(diào)用返回1,第2次調(diào)用返回2,第3次及之后的調(diào)用都返回3// when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3),// 可簡(jiǎn)寫(xiě)為:when(mockList.size()).thenReturn(1, 2, 3);Assertions.assertEquals(1, mockList.size());Assertions.assertEquals(2, mockList.size());Assertions.assertEquals(3, mockList.size());Assertions.assertEquals(3, mockList.size());}
}

5) 實(shí)戰(zhàn)

package com.lkcoffee.framework.demo2;import com.lkcoffee.framework.demo2.domain.User;
import com.lkcoffee.framework.demo2.mapper.UserMapper;
import com.lkcoffee.framework.demo2.service.UserService;
import com.lkcoffee.framework.demo2.service.impl.UserServiceImpl;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;import java.util.List;import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;@SpringBootTest(classes = Demo2Application.class)
class UserServiceImplTest   {@MockBeanprivate UserMapper userMapper;@Resource@SpyBeanprivate UserServiceImpl userService;@BeforeEachvoid setUp() {// 這一步是為了解決mybatisplus 中沒(méi)有baseMapper的問(wèn)題// 因?yàn)槭抢^承了ServiceImpl 。是父類(lèi)。InjectMocks無(wú)法注入父類(lèi)的屬性// 如果使用了Autowired 的 Resource ,就不需要這一步了// doReturn(userMapper).when(userService).getBaseMapper();}@Testvoid testQueryAll() {// 模擬查詢(xún)結(jié)果when(userMapper.selectList(any())).thenReturn(List.of(new User(1L, "Alice", 25,"203462009@qq.com"),new User(2L, "Bob", 30,"203462008@qq.com")));// 執(zhí)行查詢(xún)var result = userService.queryAll();// 驗(yàn)證查詢(xún)結(jié)果assertEquals(2, result.size());assertEquals("Alice", result.get(0).getName());assertEquals("Bob", result.get(1).getName());}@Testvoid testQueryById() {// 模擬查詢(xún)結(jié)果when(userMapper.selectById(1L)).thenReturn(new User(1L, "Alice", 25,"203462009@qq.com"));// 執(zhí)行查詢(xún)var result = userService.queryById(1L);// 驗(yàn)證查詢(xún)結(jié)果assertEquals("Alice", result.getName());}@Testvoid testAddUser() {// 創(chuàng)建一個(gè)用戶(hù)對(duì)象User user = new User(null, "Alice", 25,"203462009@qq.com");// 模擬save方法返回結(jié)果when(userMapper.insert(user)).thenReturn(1);// 執(zhí)行添加用戶(hù)var result = userService.addUser(user);// 驗(yàn)證添加結(jié)果assertTrue(result);}
}

image-20240712164239982

3.3 Springboot測(cè)試注解

@MockBean

@MckBean是Spring Boot提供的注解,專(zhuān)門(mén)用于在Spring應(yīng)用上下文中添加或替換一個(gè)bean為mock對(duì)象。這個(gè)注解主要用于集成測(cè)試場(chǎng)景,特別是當(dāng)測(cè)試需要Spring環(huán)境支持時(shí),如測(cè)試控制器與服務(wù)層的交互等。

  1. 使用@MockBean注解的的對(duì)象,會(huì)生成一個(gè)Mock的bean.不會(huì)生成原來(lái)的bean
  2. 并會(huì)將該bean注入到依賴(lài)該bean的其他bean中
  3. 正常的bean還是會(huì)正常組裝注入

Spring Boot 中@Mock 和@MockBean 注解的主要區(qū)別

  • @Mock用于模擬不屬于 Spring 上下文的對(duì)象,而 @MockBean用于模擬屬于一部分的對(duì)象Spring上下文的。它用于帶有 Mockito 框架的普通 JUnit 測(cè)試。它也不知道 Spring 上下文,通常用于單元測(cè)試隔離組件,而不需要完整的 Spring 上下文設(shè)置。
  • @MockBean是一個(gè) Spring Boot 特定的注釋,它提供與 Spring Boot 測(cè)試模塊的集成,允許在 Spring Boot 應(yīng)用程序中無(wú)縫模擬 Spring bean。
  • @Mock需要使用 MockitoJUnitRunner 或 MockitoExtension 來(lái)初始化模擬對(duì)象,而@MockBean在測(cè)試上下文設(shè)置期間由 Spring Boot 測(cè)試框架自動(dòng)初始化。
  • @MockBean在測(cè)試時(shí)將Spring上下文中的實(shí)際bean替換為mock對(duì)象,而@Mock不影響Spring上下文中的實(shí)際bean

@SpyBean

@SpringBootTest(classes = AppBootStrap.class)
public class AbstractTestCase {}/*** 1。使用@MockBean注解的的對(duì)象,會(huì)生成一個(gè)Mock的bean.不會(huì)生成原來(lái)的bean* 2。并會(huì)將該bean注入到依賴(lài)該bean的其他bean中* 3。正常的bean還是會(huì)正常組裝注入*/
public class HelloControllerMockBeanTest extends AbstractTestCase {@Autowiredprivate HelloController helloController;@MockBeanprivate HelloService helloService;@Testpublic void testHello(){System.out.println("============only junit5================");helloController.hello();System.out.println("============only junit5================");}
}/*** 1。使用@MockBean注解的的對(duì)象,會(huì)生成一個(gè)spy的bean行為與原類(lèi)型一致.不會(huì)生成原來(lái)的bean* 2。并會(huì)將該bean注入到依賴(lài)該bean的其他bean中* 3。正常的bean還是會(huì)正常組裝注入*/
public class HelloControllerSpyBeanTest extends AbstractTestCase {@Autowiredprivate HelloController helloController;@SpyBeanprivate HelloService helloService;@Testpublic void testHello(){System.out.println("============only junit5================");helloController.hello();System.out.println("============only junit5================");}
}
http://www.risenshineclean.com/news/44345.html

相關(guān)文章:

  • 網(wǎng)站開(kāi)發(fā)目錄結(jié)構(gòu)百度首頁(yè)排名怎么做到
  • 做ppt模板網(wǎng)站有哪些網(wǎng)站統(tǒng)計(jì)
  • 做自己網(wǎng)站彩票免費(fèi)站長(zhǎng)工具
  • 寶安有效的網(wǎng)站制作站長(zhǎng)域名查詢(xún)工具
  • python源碼分享網(wǎng)站百度客服24小時(shí)人工服務(wù)
  • wordpress消息系統(tǒng)滕州網(wǎng)站建設(shè)優(yōu)化
  • 淘寶開(kāi)店網(wǎng)站怎么做網(wǎng)絡(luò)稿件投稿平臺(tái)
  • 可以做照片書(shū)的網(wǎng)站百度推廣入口
  • next 主題wordpress谷歌seo招聘
  • 用內(nèi)網(wǎng)穿透做網(wǎng)站可以被收錄嗎淘寶關(guān)鍵詞搜索工具
  • 懷柔網(wǎng)站制作煙臺(tái)seo網(wǎng)絡(luò)推廣
  • 淄博 網(wǎng)站建設(shè)免費(fèi)網(wǎng)站在線客服系統(tǒng)源碼
  • 貴州住房和城鄉(xiāng)建設(shè)部網(wǎng)站官網(wǎng)阿里關(guān)鍵詞排名查詢(xún)
  • 德升武漢網(wǎng)站建設(shè)推廣哪個(gè)網(wǎng)站好
  • 談?wù)劸W(wǎng)站的開(kāi)發(fā)流程長(zhǎng)沙網(wǎng)站優(yōu)化seo
  • 網(wǎng)站建設(shè) 書(shū)籍下載廣告推廣方案怎么寫(xiě)
  • 做一個(gè)網(wǎng)站人員seo運(yùn)營(yíng)是什么
  • 深圳鹽田建設(shè)交易中心網(wǎng)站什么叫軟文
  • wwe中文官網(wǎng)站網(wǎng)站一級(jí)域名和二級(jí)域名區(qū)別
  • 廣州行業(yè)網(wǎng)站建設(shè)武漢seo公司出 名
  • 開(kāi)發(fā)一個(gè)企業(yè)網(wǎng)站需要多少錢(qián)百度認(rèn)證服務(wù)平臺(tái)
  • 車(chē)牌照損壞在網(wǎng)站做的能用嗎吉林seo外包
  • 網(wǎng)站建設(shè)成本價(jià)長(zhǎng)沙免費(fèi)建站網(wǎng)絡(luò)營(yíng)銷(xiāo)
  • 輿情監(jiān)測(cè)系統(tǒng)永久免費(fèi)seo整站優(yōu)化哪家專(zhuān)業(yè)
  • 河南網(wǎng)絡(luò)推廣那家好煙臺(tái)seo快速排名
  • 做企業(yè)網(wǎng)站開(kāi)發(fā)哪家好網(wǎng)絡(luò)推廣工作室
  • 怎么用vps搭建網(wǎng)站無(wú)錫百度信息流
  • 成都網(wǎng)站開(kāi)發(fā)價(jià)格沈陽(yáng)seo整站優(yōu)化
  • 連云港做網(wǎng)站公司哪家好推廣文案
  • 天津平臺(tái)網(wǎng)站建設(shè)制作班級(jí)優(yōu)化大師的利和弊