单元测试Mockito笔记

文章目录

  • 单元测试Mockito
  • 1. 入门
    • 1.1 什么是Mockito
    • 1.2 优势
    • 1.3 原理
  • 2. 使用
    • 2.0 环境准备
    • 2.1 Mock
      • 1) Mock对象创建
      • 2) 配置Mock对象的行为(打桩)
      • 3) 验证方法调用
      • 4) 参数匹配
      • 5) 静态方法
    • 2.2 常用注解
      • 1) @Mock
      • 2) @BeforeEach 与 @BeforeAfter
      • 3) @InjectMocks
      • 4) @Spy
      • 5) @Captor
      • 6) @RunWith和@ExtendWith
        • @RunWith
        • @ExtendWith
    • 2.3 常见区别
      • Mock对象和Spy对象区别
  • 3. Springboot 使用
    • 3.1 数据准备
      • 创建sql
      • 引入依赖
      • 添加application.yml
      • 编写实体类
      • 编写Service层
      • 编写controller
    • 3.2 测试
      • 1) 创建Mock或者Spy对象
        • 方法一
        • 方法二
        • 方法三
      • 2) 参数匹配
      • 3) 打桩
      • 4) 多次打桩
      • 5) 实战
    • 3.3 Springboot测试注解
      • @MockBean
      • @SpyBean

单元测试Mockito

名称链接备注
mockito英文文档Mockito (Mockito 5.12.0 API) (javadoc.io)
mockito中文文档Mockito 中文文档 ( 2.0.26 beta ) - 《Mockito 框架中文文档》 - 极客文档 (geekdaxue.co)
视频教学链接https://www.bilibili.com/video/BV1P14y1k7Hi

1. 入门

1.1 什么是Mockito

Mockito是Java生态系统中最受欢迎的单元测试模拟框架之一,以其简洁易用的API和强大的模拟能力赢得了广大开发者的青睐。Mockito允许我们在不实际依赖外部资源的情况下对代码进行彻底且高效的单元测试,极大地提升了测试覆盖率和代码质量。

1.2 优势

Mockito是一种模拟框架,其核心概念是在测试过程中创建并使用“Mock对象”。Mock对象是对实际对象的一种模拟,它继承或实现了被测试类所依赖的接口或类,但其行为可以根据测试需求自由定制。控制其在测试环境下的行为,从而将注意力聚焦于类本身的逻辑验证上。

  • 隔离度高:通过模拟依赖,减少测试间的耦合,确保单元测试真正只关注被测试单元的内部逻辑。
  • 易于使用:API设计直观简洁,降低了编写和阅读测试用例的难度。
  • 详尽的验证:能够准确跟踪和验证被测试对象与其依赖之间的交互行为。
  • 灵活性强:支持多种定制模拟行为,无论是简单的返回值还是复杂的回调机制。
  • 有利于TDD实践:与测试驱动开发方法论紧密契合,鼓励写出更易于测试的代码。

1.3 原理

Mockito 的底层原理是使用 cglib 动态生成一个 代理类对象,因此,mock 出来的对象其实质就是一个 代理,该代理在 没有配置/指定行为 的情况下,默认返回空值

2. 使用

2.0 环境准备

创建一个普通的maven项目。添加依赖

<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对象创建

使用Mockito.mock()方法创建接口或抽象类的Mock对象。下面是它的方法接口

public static <T> T mock(Class<T> classToMock)
  • classToMock:待 mock 对象的 class 类。
  • 返回 mock 出来的类

实例:使用 mock 方法 mock 一个类

import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;public class MyTest {@Testpublic void myTest() {/* 创建 Mock 对象 */List list = mock(List.class);/* 设置预期,当调用 get(0) 方法时返回 "111" */when(list.get(0)).thenReturn("111");Assert.assertEquals("asd", 1, 1);/* 设置后返回期望的结果 */System.out.println(list.get(0));/* 没有设置则返回 null */System.out.println(list.get(1));/* 对 Mock 对象设置无效 */list.add("12");list.add("123");/* 返回之前设置的结果 */System.out.println(list.get(0));/* 返回 null */System.out.println(list.get(1));/* size 大小为 0 */System.out.println(list.size());/* 验证操作,验证 get(0) 调用了 2 次 */verify(list, times(2)).get(0);/* 验证返回结果 */String ret = (String)list.get(0);Assert.assertEquals(ret, "111");}
}  

总结

junit4junit5
方法一@RunWith(MockitojUnitRunner.class)+@Mock等注解@ExtendWith(MockitoExtension.class)+@Mock等注解
方法二Mockito.mock(X.class)MockitoAnnotations.open等静态方法Mockito.mock(X.class)MockitoAnnotations.open等静态方法
方法三Mocks(this)+@Mock等注解Mocks(this)+@Mock等注解

2) 配置Mock对象的行为(打桩)

使用whenthenReturn方法配置Mock对象的行为:

打桩可以理解为mock对象规定一行的行为,使其按照我们的要求来执行具体的操作。在Mockito中,常用的打桩方法为

方法含义
when().thenReturn()Mock 对象在触发指定行为后返回指定值
when().thenThrow()Mock 对象在触发指定行为后抛出指定异常
when().doCallRealMethod()Mock 对象在触发指定行为后调用真实的方法

thenReturn() 代码示例

    public void test02(){// 模拟random对象,这个对象是假的Random random = Mockito.mock(Random.class);// 当调用了random对象时,返回100这个值Mockito.when(random.nextInt()).thenReturn(100);// 验证,应该是对的。有人会问,random.nextInt()不是获取随机值吗?// 现在这个random对象是假的Assertions.assertEquals(100, random.nextInt());}

完整的另一个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("测试了a+b  a="+a+",b="+b);return a+b;}}@Testvoid testAdd() {MockitoTestController mockitoTestController = mock(MockitoTestController.class);// 设置mock对象的行为(打桩),当调用add(1, 2)时返回4when(mockitoTestController.add(1, 2)).thenReturn(4);// 调用mock对象的方法,返回为4int result = mockitoTestController.add(1, 2);log.info("mockitoTestController.add result={}",result);// 断言验证:调用add(1, 2)方法返回值是否为4Assertions.assertEquals(mockitoTestController.add(1, 2),4);// 验证:确保add方法(1, 2)被调用了一次verify(mockitoTestController,times(2)).add(1, 2);}}

你还可以配置方法抛出异常:

 /*** 测试当调用add方法时抛出RuntimeException异常的情况。* 该测试函数不接受参数,也没有返回值。*/@Testvoid testAddException() {TestController mockitoTestController = Mockito.mock(TestController.class);// 设置mock对象,在调用mockitoTestController的add方法时抛出RuntimeException异常when(mockitoTestController.add(1, 2)).thenThrow(new RuntimeException("add error"));// 验证是否抛出了RuntimeException异常Assertions.assertThrows(RuntimeException.class, () -> mockitoTestController.add(1, 2));}public static class TestController{public int add(int a, int b){System.out.println("测试了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) 验证方法调用

Mock对象进行行为验证和结果断言。验证是校验对象是否发生过某些行为,Mockito 中验证的方法是:verify

常见的验证方法包括:

  • verify(mock).methodCall():验证方法被调用
  • verify(mock, times(n)).methodCall():验证方法被调用n次
  • verify(mock, never()).methodCall():验证方法从未被调用

验证交换:Verify 配合 time() 方法,可以校验某些操作发生的次数。
注意:当使用 mock 对象时,如果不对其行为进行定义,则 mock 对象方法的返回值为返回类型的默认值。

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模拟一个Random对象Random random = Mockito.mock(Random.class);// 调用nextInt()方法,输出随机数,因random 行为为进行打桩,故输出默认值0(random.nextInt() 返回的是int类型)System.out.println("第一次:"+random.nextInt());// 验证random.nextInt()这个方法是否只调用了一次verify(random).nextInt();// 指定当调用nextInt()时,始终返回1Mockito.when(random.nextInt()).thenReturn(1);System.out.println("第二次:"+random.nextInt()); // 再次调用nextInt(),输出应为1// 断言nextInt()方法返回值是否为1Assertions.assertEquals(1,random.nextInt());// 验证nextInt()方法是否被调用了两次verify(random, times(3)).nextInt();}
}

4) 参数匹配

Mockito提供了多种参数匹配器(Matchers)用于更灵活的验证和配置行为:

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

常见的匹配器包括:

  • any():匹配任何参数
  • anyInt():匹配任何整数参数
  • eq(value):匹配特定值
  • isNull():匹配null值
  • notNull():匹配非null值

5) 静态方法

添加依赖

    <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版本低的话,版本可以低一点.

使用 mockStatic() 方法来 mock静态方法的所属类,此方法返回一个具有作用域的模拟对象。

    @Testpublic void testJoinWith() {// 使用 Mockito 框架模拟 StringUtils 类的静态方法MockedStatic<StringUtils> stringUtilsMockedStatic = Mockito.mockStatic(StringUtils.class);// 创建一个字符串列表,作为 joinWith 方法的输入参数List<String> stringList = Arrays.asList("a", "b", "c");// 配置模拟行为,当调用 StringUtils.joinWith(",", stringList) 时,返回 "a,b,c"stringUtilsMockedStatic.when(() -> StringUtils.joinWith(",", stringList)).thenReturn("a,b,c");// 断言验证模拟行为是否正确,即 joinWith 方法返回的字符串是否与预期的 "a,b,c" 相等Assertions.assertTrue(StringUtils.joinWith(",", stringList).equals("a,b,c"));}

但是如果你写成下面这样子的话,会发送报错

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 类的静态方法MockedStatic<StringUtils> stringUtilsMockedStatic = Mockito.mockStatic(StringUtils.class);// 创建一个字符串列表,作为 joinWith 方法的输入参数List<String> stringList = Arrays.asList("a", "b", "c");// 配置模拟行为,当调用 StringUtils.joinWith(",", stringList) 时,返回 "a,b,c"stringUtilsMockedStatic.when(() -> StringUtils.joinWith(",", stringList)).thenReturn("a,b,c");// 断言验证模拟行为是否正确,即 joinWith 方法返回的字符串是否与预期的 "a,b,c" 相等Assertions.assertTrue(StringUtils.joinWith(",", stringList).equals("a,b,c"));}/*** 测试StringUtils类中的join方法。* 该测试使用Mockito框架来模拟静态方法的行为,验证join方法是否按照预期工作。* */@Testpublic void testJoin() {// 使用Mockito模拟StringUtils类的静态方法MockedStatic<StringUtils> stringUtilsMockedStatic = Mockito.mockStatic(StringUtils.class);// 创建一个字符串列表作为join方法的输入List<String> stringList = Arrays.asList("a", "b", "c");// 配置模拟行为,当调用StringUtils.join(",", stringList)时,返回字符串"a,b,c"stringUtilsMockedStatic.when(() -> StringUtils.join(",", stringList)).thenReturn("a,b,c");// 断言验证模拟行为是否正确,即 join 方法返回的字符串是否与预期的 "a,b,c" 相等Assertions.assertTrue(StringUtils.join(",", stringList).equals("a,b,c"));}}

然后执行整个测试类后会报错:,就会报错

image-20240712094211482

原因是因为 mockStatic() 方法是将当前需要 mock 的类注册到本地线程上(ThreadLocal),而这个注册在一次 mock 使用完之后是不会消失的,需要我们手动的去销毁。如过没有销毁,再次 mock 这个类的时候 Mockito 将会提示我们 :”当前对象 mock 的对象已经在线程中注册了,请先撤销注册后再试“。这样做的目的也是为了保证模拟出来的对象之间是相互隔离的,保证同时和连续的测试不会收到上下文的影响。

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);}/*** 测试Mockito框架的使用,模拟Random类的nextInt方法。* 该测试函数没有参数和返回值,主要用于演示Mockito的基本用法。*/@Testpublic void test02() {// 调用nextInt()方法,输出随机数,因random 行为为进行打桩,故输出默认值0(random.nextInt() 返回的是int类型)System.out.println("第一次:"+random.nextInt());// 指定当调用nextInt()时,始终返回1Mockito.when(random.nextInt()).thenReturn(1);System.out.println("第二次:"+random.nextInt()); // 再次调用nextInt(),输出应为1// 断言nextInt()方法返回值是否为1Assertions.assertEquals(1,random.nextInt());// 验证nextInt()方法是否被调用了两次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("==============测试前准备===============");MockitoAnnotations.openMocks(this);}/*** 测试Mockito框架的使用,模拟Random类的nextInt方法。* 该测试函数没有参数和返回值,主要用于演示Mockito的基本用法。*/@Testpublic void test02() {// 调用nextInt()方法,输出随机数,因random 行为为进行打桩,故输出默认值0(random.nextInt() 返回的是int类型)System.out.println("第一次:"+random.nextInt());// 指定当调用nextInt()时,始终返回1Mockito.when(random.nextInt()).thenReturn(1);System.out.println("第二次:"+random.nextInt()); // 再次调用nextInt(),输出应为1// 断言nextInt()方法返回值是否为1Assertions.assertEquals(1,random.nextInt());// 验证nextInt()方法是否被调用了两次verify(random, times(3)).nextInt();}@AfterEachvoid tearDown() {log.info("==============测试后结果===============");}}

image-20240712095557244

3) @InjectMocks

@InjectMocks用于将模拟对象注入到被测试类中的相应字段。通过该注解可以自动将模拟对象注入到被测试类中标记为@InjectMocks的字段中,可以理解为使用@Mock创建出来的对象注入到@InjectMocks创建的对象中,这样被测试类就可以使用模拟对象作为其依赖了。

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() {// 当调用a方法时,直接返回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) {// 调用a方法int add = aClass.add();System.out.println("测试了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 的对象会走真实的方法,而 mock 对象不会
  2. spy() 方法的参数是对象实例,mock 的参数是 class

首先,我们使用mock方法。做一个测试

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("测试了a+b  a="+a+",b="+b);return a+b;}}@Testpublic void test01() {MockitoTestController   mockitoTestController =new MockitoTestController();// 调用实际的 mockitoTestController 对象的 add 方法,并验证结果是否为预期值int result = mockitoTestController.add(1, 2);Assertions.assertEquals(3, result);// 使用 Mockito 创建 mockitoTest 的 mock 对象,并对它调用 add 方法,然后验证结果MockitoTestController mockitoTest = Mockito.mock(MockitoTestController.class);int result1 = mockitoTest.add(1, 2);Assertions.assertEquals(3, result1);}}

返回的结果

第二个 Assertions 断言失败,因为没有给 mockitoTest 对象打桩,因此返回默认值

image-20240712100357578

使用@Spy()注解示例。引入依赖

<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>

代码测试

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("测试了a+b  a="+a+",b="+b);return a+b;}}@Spyprivate MockitoTestController mockitoTestController;@BeforeEachvoid setUp() {}/*** 测试add方法* 该方法模拟调用mockitoTestController的add方法,传入参数1和2,期望返回值为3。* 首先,通过when语句设置mockitoTestController的add方法返回值为3;* 然后,使用assertThat断言验证调用add方法(1, 2)实际返回值确实为3;* 最后,通过verify语句确认mockitoTestController的add方法确实被调用了一次,并传入了参数1和2。*/@Testvoid testAdd() {// 设置mock对象的行为(打桩),当调用add(1, 2)时返回4when(mockitoTestController.add(1, 2)).thenReturn(4);// 调用mock对象的方法,返回为4int result = mockitoTestController.add(1, 2);log.info("mockitoTestController.add result={}",result);// 断言验证:调用add(1, 2)方法返回值是否为4Assertions.assertEquals(mockitoTestController.add(1, 2),4);// 验证:确保add方法(1, 2)被调用了一次verify(mockitoTestController,times(2)).add(1, 2);}}

5) @Captor

接下来,我们来看看如何使用@Captor注解来创建ArgumentCaptor实例。

在以下示例中,我们将在不使用@Captor注释的情况下创建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来创建一个ArgumentCaptor实例:

    @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

测试类上使用 @RunWith(SpringRunner.class) 注解(使用的是 JUnit 4)
测试类上使用 @ExtendWith(SpringExtension.class)注解(使用的是 JUnit 5)

SpringBoot2.4.x之后,改为默认仅集成JUnit5,干掉了兼容JUnit4

@RunWith
  • @RunWith就是一个运行器
  • @RunWith(JUnit4.class)就是指用JUnit4来运行
  • @RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,以便在测试开始的时候自动创建Spring的应用上下文
@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;// 定义一个自定义的JUnit扩展,用于在测试开始前输出日志
class CustomExtension implements BeforeTestExecutionCallback {@Overridepublic void beforeTestExecution(ExtensionContext context) {System.out.println("Before Test Execution");}
}// 使用@ExtendWith注解加载自定义扩展
@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结合使用,特别是JUnit 5,利用@ExtendWith(MockitoExtension.class)简化Mock对象的初始化

启动类加上@ExtendWith(MockitoExtension.class),会自动处理@Mock@Spy@InjectMocks等注解

import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;@ExtendWith(MockitoExtension.class)
public class UserServiceTest {// 测试代码
}

2.3 常见区别

Mock对象和Spy对象区别

方法插桩方法不插桩作用对象最佳实践
mock对象执行插桩逻辑返回mock对象的默认值类、接口被测试类或其依赖
spy对象执行插桩逻辑调用真实方法类、接口被测试类

3. Springboot 使用

首先看下完整的pom结构

image-20240712113509616

3.1 数据准备

创建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');

引入依赖

创建springboot 项目。

<?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 启动类中添加 @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);}}

编写实体类

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

编写 Mapper 接口类 UserMapper.java

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

编写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: 用户服务层* @Author: feixiang.li* @date: 2024-07-11 19:51**/
public interface UserService  extends IService<User> {/*** 查询所有用户信息* @return 所有用户信息*/List<User> queryAll();/*** 根据用户id查询* @param id 用户id* @return 用户信息*/User queryById(Long id);/*** 添加用户id* @param user 用户信息* @return 操作结果*/Boolean addUser(User user);/*** 根据用户id修改用户信息* @param user* @return*/Integer updateUser(User user);
}

实现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: 用户操作类* @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("被真实调用了, 执行了 查询所有用户信息");return list();}@Overridepublic User queryById(Long id) {log.info("被真实调用了, 根据用户id:{} 查询用户",id);return getById(id);}@Transactional(rollbackFor = Exception.class)@Overridepublic Boolean addUser(User user) {log.info("被真实调用了, 添加用户信息:{}",user);if(Objects.nonNull(user.getId())){throw new RuntimeException("被真实调用了,新增用户,id应该为空");}if(Objects.isNull(user.getAge()) || user.getAge() < 0 || user.getAge() > 100){throw new RuntimeException("被真实调用了,请填写正确的年龄");}if(StringUtils.isBlank(user.getName())){throw new RuntimeException("被真实调用了,对不起,姓名不能为空");}return save(user);}@Transactional(rollbackFor = Exception.class)@Overridepublic Integer updateUser(User user) {System.out.println("执行了真实的更新用户方法");int result= getBaseMapper().updateById(user);System.out.println("update user result:"+result);return result;}}

编写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 "对象为空";}userService.save(user);return "success";}
}

启动项目: 访问下面

http://localhost:8080/user

返回一下结果,说明项目启动成功;

image-20240712105244873

3.2 测试

1) 创建Mock或者Spy对象

junit4junit5
方法一@RunWith(MockitojUnitRunner.class)+@Mock等注解@ExtendWith(MockitoExtension.class)+@Mock等注解
方法二Mockito.mock(X.class)MockitoAnnotations.open等静态方法Mockito.mock(X.class)MockitoAnnotations.open等静态方法
方法三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(){// 判断某个对象是不是mock对象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(){// 判断某个对象是不是mock对象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(){// 判断某个对象是不是mock对象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)

这两个效果一样,只是在juit5中initMocks被抛弃了

MockitoAnnotations.initMocks(this)方法并不会产生代理类,它主要是用于初始化Mockito注解。在测试中,我们通常使用@Mock、@Spy、@InjectMocks等注解来创建Mock对象,并使用Mockito.when、Mockito.verify等方法来模拟对象的行为和验证方法调用。

但是,如果我们不调用MockitoAnnotations.initMocks(this)方法,这些Mock对象就无法被正确初始化,从而导致测试失败。因此,我们通常在@Before注解方法中调用这个方法,以确保所有的Mock对象都已经被正确初始化。

在具体实现中,MockitoAnnotations.initMocks(this)方法会扫描测试类中所有的@Mock、@Spy、@InjectMocks注解,并根据注解中的类型和名称来创建对应的Mock对象,并将这些对象注入到测试类中。这样,在测试过程中就可以使用这些Mock对象来模拟外部依赖,从而实现单元测试的独立性和可重复性。

2) 参数匹配

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;/*** 参数匹配:通过方法签名(参数)来制定哪些方法调用需要被处理*/
@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对象不会调用真实方法*/User user = new User();user.setId(1L);user.setName("fly");doReturn(99).when(mockUserService).updateUser(user);int result1 = mockUserService.updateUser(user);log.info("用户1修改对象返回值:{}", result1);User user2 = new User();user.setId(2L);user.setName("name2");int result2 = mockUserService.updateUser(user2);log.info("用户2修改对象返回值:{}", result2);// 现在我想任意用户都返回99doReturn(99).when(mockUserService).updateUser(any());result1 = mockUserService.updateUser(user);result2 = mockUserService.updateUser(user2);log.info("用户1修改对象返回值:{}", result1);log.info("用户2修改对象返回值:{}", result2);}@Testpublic void test1() {/*** 这里返回值是null. Mock对象不会调用真实方法。如果不进行插桩的话*/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("调用了真实方法 测试了a+b  a="+a+",b="+b);return a+b;}}@Spyprivate MockitoTestController spyMockitoTestController;@BeforeEachvoid setUp() {}/*** 测试add方法* 该方法模拟调用mockitoTestController的add方法,传入参数1和2,期望返回值为3。* 首先,通过when语句设置mockitoTestController的add方法返回值为3;* 然后,使用assertThat断言验证调用add方法(1, 2)实际返回值确实为3;* 最后,通过verify语句确认mockitoTestController的add方法确实被调用了一次,并传入了参数1和2。*/@Testvoid testAdd() {// 设置mock对象的行为(打桩),当调用add(1, 2)时返回4// 虽然使用了when ,但是已经调用了真实方法when(spyMockitoTestController.add(1, 2)).thenReturn(4);// 调用mock对象的方法,返回为4int result = spyMockitoTestController.add(1, 2);log.info("mockitoTestController.add result={}",result);// 断言验证:调用add(1, 2)方法返回值是否为4Assertions.assertEquals(spyMockitoTestController.add(1, 2),4);// 验证:确保add方法(1, 2)被调用了一次verify(spyMockitoTestController,times(2)).add(1, 2);/*** spy对象在没有摄性时是谓用真实方法的,号加en中会导致先技行一次方法,达不打桩的目的* 需使用 doXxx().when(obj).someNethod()*/doReturn(99).when(spyMockitoTestController).add(anyInt(),anyInt());int result2 = spyMockitoTestController.add(1, 2);log.info("spyMockitoTestController.add result={}",result2);}}

如果使用springboot的话,低端用法,没有使用@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的问题。手动把mapper注入进去。// 如果使用了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对象在没有摄性时是谓用真实方法的,号加en中会导致先技行一次方法,达不och的目的* 需使用 doXxx().when(obj).someNethod()*/doReturn(100).when(spyUserService).updateUser(any());int result3 = spyUserService.updateUser(user);System.out.println("result3 = " + result3);}
}

执行结果对象

result1 = 99
执行了真实的更新用户方法
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次调用返回1,第2次调用返回2,第3次及之后的调用都返回3// when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3),// 可简写为: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) 实战

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 中没有baseMapper的问题// 因为是继承了ServiceImpl 。是父类。InjectMocks无法注入父类的属性// 如果使用了Autowired 的 Resource ,就不需要这一步了// doReturn(userMapper).when(userService).getBaseMapper();}@Testvoid testQueryAll() {// 模拟查询结果when(userMapper.selectList(any())).thenReturn(List.of(new User(1L, "Alice", 25,"203462009@qq.com"),new User(2L, "Bob", 30,"203462008@qq.com")));// 执行查询var result = userService.queryAll();// 验证查询结果assertEquals(2, result.size());assertEquals("Alice", result.get(0).getName());assertEquals("Bob", result.get(1).getName());}@Testvoid testQueryById() {// 模拟查询结果when(userMapper.selectById(1L)).thenReturn(new User(1L, "Alice", 25,"203462009@qq.com"));// 执行查询var result = userService.queryById(1L);// 验证查询结果assertEquals("Alice", result.getName());}@Testvoid testAddUser() {// 创建一个用户对象User user = new User(null, "Alice", 25,"203462009@qq.com");// 模拟save方法返回结果when(userMapper.insert(user)).thenReturn(1);// 执行添加用户var result = userService.addUser(user);// 验证添加结果assertTrue(result);}
}

image-20240712164239982

3.3 Springboot测试注解

@MockBean

@MckBean是Spring Boot提供的注解,专门用于在Spring应用上下文中添加或替换一个bean为mock对象。这个注解主要用于集成测试场景,特别是当测试需要Spring环境支持时,如测试控制器与服务层的交互等。

  1. 使用@MockBean注解的的对象,会生成一个Mock的bean.不会生成原来的bean
  2. 并会将该bean注入到依赖该bean的其他bean中
  3. 正常的bean还是会正常组装注入

Spring Boot 中@Mock 和@MockBean 注解的主要区别

  • @Mock用于模拟不属于 Spring 上下文的对象,而 @MockBean用于模拟属于一部分的对象Spring上下文的。它用于带有 Mockito 框架的普通 JUnit 测试。它也不知道 Spring 上下文,通常用于单元测试隔离组件,而不需要完整的 Spring 上下文设置。
  • @MockBean是一个 Spring Boot 特定的注释,它提供与 Spring Boot 测试模块的集成,允许在 Spring Boot 应用程序中无缝模拟 Spring bean。
  • @Mock需要使用 MockitoJUnitRunner 或 MockitoExtension 来初始化模拟对象,而@MockBean在测试上下文设置期间由 Spring Boot 测试框架自动初始化。
  • @MockBean在测试时将Spring上下文中的实际bean替换为mock对象,而@Mock不影响Spring上下文中的实际bean

@SpyBean

@SpringBootTest(classes = AppBootStrap.class)
public class AbstractTestCase {}/*** 1。使用@MockBean注解的的对象,会生成一个Mock的bean.不会生成原来的bean* 2。并会将该bean注入到依赖该bean的其他bean中* 3。正常的bean还是会正常组装注入*/
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注解的的对象,会生成一个spy的bean行为与原类型一致.不会生成原来的bean* 2。并会将该bean注入到依赖该bean的其他bean中* 3。正常的bean还是会正常组装注入*/
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.mzph.cn/news/871059.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据分析——Python网络爬虫(四){正则表达式}

爬虫库的使用 爬虫的步骤正则表达式正则表达式的流程正则表达式的使用括号的使用管道匹配问号匹配星号匹配加号匹配花括号匹配用点-星匹配所有字符跨行匹配findall方法其他常用字符匹配 例子正则表达式在线测试 爬虫的步骤 #mermaid-svg-zSQSbTxUEex051NQ {font-family:"t…

“汇聚全球智慧·引领未来科技”2024南京人工智能展会

南京&#xff0c;这座古老而又现代的城市&#xff0c;自古以来便是江南繁华的代名词。如今&#xff0c;随着科技的飞速发展&#xff0c;南京再次站在了时代的潮头&#xff0c;以其深厚的历史底蕴和不断创新的科技力量&#xff0c;成为了全球瞩目的焦点。而在即将到来的2024年&a…

数学建模--数据统计类赛题分析~~神经网络引入

1.缺失值的处理 &#xff08;1&#xff09;像在下面的这个表格里面&#xff0c;这个对于缺失的数据&#xff0c;我们需要分情况进行分析&#xff0c;如果这个数据就是一个数值型的数据&#xff0c;我们可以使用平均值进行处理&#xff1b; &#xff08;2&#xff09;对于这个…

用python识别二维码(python实例二十三)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.识别二维码 3.1 代码构思 3.2 代码实例 3.3 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&…

MongoDB教程(三):mongoDB用户管理

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…

线性代数|机器学习-P23梯度下降

文章目录 1. 梯度下降[线搜索方法]1.1 线搜索方法&#xff0c;运用一阶导数信息1.2 经典牛顿方法&#xff0c;运用二阶导数信息 2. hessian矩阵和凸函数2.1 实对称矩阵函数求导2.2. 线性函数求导 3. 无约束条件下的最值问题4. 正则化4.1 定义4.2 性质 5. 回溯线性搜索法 1. 梯度…

【正点原子i.MX93开发板试用连载体验】录音小程序采集语料

本文最早发表于电子发烧友论坛&#xff1a;【新提醒】【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com) 接下来就是要尝试训练中文提示词。首先要进行语料采集&#xff0c;这是一…

LLM大模型从入门到精通(3)--LLM主流大模型类别

目录 1 ChatGLM-6B模型简介&#xff1a; 2 LLaMA模型简介&#xff1a; 3 BLOOM模型简介 4 Baichuan-7B模型 随着ChatGPT迅速火爆&#xff0c;引发了大模型的时代变革&#xff0c;国内外各大公司也快速跟进生成式AI市场&#xff0c;近百款大模型发布及应用。开源语言大模型种…

代码随想录算法训练营Day37||动态规划part05

初识完全背包&#xff0c;和零一背包的区别就是要正序遍历背包&#xff0c;从而让物品可以反复使用。 518.零钱兑换II: 即装满价值为j的背包有几种方法&#xff0c;和494目标和几乎一致&#xff0c;只不过换成了零一背包。通过不同的遍历顺序&#xff0c;可以求出组合数&#…

Java常用的API_02(正则表达式、爬虫)

Java正则表达式 七、正则表达式7.1 格式7.1.1 字符类注意字符类示例代码1例2 7.1.2 预定义字符预定义字符示例代码例2 7.1.3 区别总结 7.2 使用Pattern和Matcher类与直接使用String类的matches方法的区别。&#xff08;1&#xff09; 使用Pattern和Matcher类示例代码 &#xff…

分布式系统—Ceph块存储系统(RBD接口)

目录 一、服务端操作 1 创建一个名为 rbd-xy101 的专门用于 RBD 的存储池 2 将存储池转换为 RBD 模式 3 初始化存储池 4 创建镜像 5 管理镜像 6.Linux客户端使用 在管理节点创建并授权一个用户可访问指定的 RBD 存储池 ​编辑修改RBD镜像特性&#xff0c;CentOS7默认情…

Matlab结合ChatGPT—如何计算置信区间?

​前面分享了带置信区间的折线图和带置信区间的折线散点图的绘图教程&#xff1a; 很多人表示&#xff0c;昆哥&#xff0c;图是很好看啦&#xff0c;但咱不会求置信区间啊&#xff0c;咋办嘞&#xff1f; 说实话&#xff0c;这种事情属于数据处理&#xff0c;一般都是在画图前…

家政服务小程序:提高家政服务,新商机!

当下&#xff0c;社会生活的节奏非常快&#xff0c;人们忙于工作&#xff0c;在日常生活家务清洁中面临着时间、精力不足的问题&#xff0c;因此对家政服务的需求日益增加&#xff0c;这也推动了家政行业的迅速发展。目前不少年轻人都开始涌入到了家政行业中&#xff0c;市场的…

HTTP协议。(HTTP-概述和特点、HTTP-请求协议、HTTP-请求数据格式、浏览器访问服务器的几种方式)

2.1 HTTP-概述 HTTP协议又分为&#xff1a;请求协议和响应协议 请求协议&#xff1a;浏览器将数据以请求格式发送到服务器 包括&#xff1a;请求行、请求头 、请求体 响应协议&#xff1a;服务器将数据以响应格式返回给浏览器 包括&#xff1a;响应行 、响应头 、响应体 2.…

重要!!!MySQL 9.0存在重大BUG!!

7/11日开源数据库软件服务商percona发布重要警告&#xff0c;最新的mysql版本存在重大bug&#xff0c;原文如下 Do Not Upgrade to Any Version of MySQL After 8.0.37 Warning! Recently, Jean-Franois Gagn opened a bug on bug.mysql.com #115517; unfortunately, the bug…

CT金属伪影去除的去噪扩散概率模型| 文献速递-基于深度学习的多模态数据分析与生存分析

Title 题目 A denoising diffusion probabilistic model for metal artifact reduction in CT CT金属伪影去除的去噪扩散概率模型 01 文献速递介绍 CT图像中的金属伪影是在CT扫描视野内存在金属物体&#xff08;如牙科填充物、骨科假体、支架、手术器械等&#xff09;时出…

探索Java网络编程精髓:UDP与TCP的实战魔法!

Java 中提供了专门的网络编程程序包 java.net&#xff0c;提供了两种通信协议&#xff1a;UDP&#xff08;数据报协议&#xff09;和 TCP&#xff08;传输控制协议&#xff09;&#xff0c;本文对两种通信协议的开发进行详细介绍。 1 UDP 介绍 UDP&#xff1a;User Datagram Pr…

css横向滚动条支持鼠标滚轮

在做视频会议的时候&#xff0c;标准模式视图会有顶部收缩的一种交互方式&#xff0c;用到了横向滚动&#xff1b;一般情况下鼠标滚轮只支持竖向滚动&#xff0c;这次写个demo是适配横向滚动&#xff1b; 效果图展示 实现横向滚动条顶部显示 <div className{style.remote_u…

【YOLO格式的数据标签,目标检测】

标签为 YOLO 格式&#xff0c;每幅图像一个 *.txt 文件&#xff08;如果图像中没有对象&#xff0c;则不需要 *.txt 文件&#xff09;。*.txt 文件规格如下: 每个对象一行 每一行都是 class x_center y_center width height 格式。 边框坐标必须是 归一化的 xywh 格式&#x…

nginx正向代理和反向代理

nginx正向代理和反向代理 正向代理以及缓存配置 代理&#xff1a;客户端不再是直接访问服务器&#xff0c;通过代理服务器访问服务端。 正向代理&#xff1a;面向客户端&#xff0c;我们通过代理服务器的IP地址访问目标服务端。 服务端只知道代理服务器的地址&#xff0c;真…