大兴网站开发网站建设咨询,网站建设代码生成器,美图秀秀在线使用,邢台优化网站排名文章目录 单元测试Mockito1. 入门1.1 什么是Mockito1.2 优势1.3 原理 2. 使用2.0 环境准备2.1 Mock1) Mock对象创建2) 配置Mock对象的行为(打桩)3) 验证方法调用4) 参数匹配5) 静态方法 2.2 常用注解1) Mock2) BeforeEach 与 BeforeAfter3) InjectMocks4) Spy5) Captor6) RunWi… 文章目录 单元测试Mockito1. 入门1.1 什么是Mockito1.2 优势1.3 原理 2. 使用2.0 环境准备2.1 Mock1) Mock对象创建2) 配置Mock对象的行为(打桩)3) 验证方法调用4) 参数匹配5) 静态方法 2.2 常用注解1) Mock2) BeforeEach 与 BeforeAfter3) InjectMocks4) Spy5) Captor6) RunWith和ExtendWithRunWithExtendWith 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测试注解MockBeanSpyBean 单元测试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 xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.ucarinc.framework/groupIdartifactIddemo1/artifactIdversion1.0-SNAPSHOT/versionpackagingjar/packagingnamedemo1/nameurlhttp://maven.apache.org/urlpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependenciesdependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdversion2.0.13/version/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-log4j12/artifactIdversion2.0.13/version/dependencydependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter/artifactIdversion5.10.2/versionscopetest/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.32/version/dependencydependencygroupIdorg.mockito/groupIdartifactIdmockito-core/artifactIdversion5.11.0/versionscopetest/scope/dependency/dependencies/project
2.1 Mock
1) Mock对象创建
使用Mockito.mock()方法创建接口或抽象类的Mock对象。下面是它的方法接口
public static T T mock(ClassT 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对象的行为(打桩)
使用when和thenReturn方法配置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(测试了ab aa,bb);return ab;}}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(测试了aba,bb);return ab;}}有种特殊情况就是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 {MockListString 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) 静态方法
添加依赖 dependencygroupIdorg.mockito/groupIdartifactIdmockito-inline/artifactIdversion5.2.0/versionscopetest/scope/dependency!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.14.0/version/dependency如果jdk版本低的话版本可以低一点.
使用 mockStatic() 方法来 mock静态方法的所属类此方法返回一个具有作用域的模拟对象。 Testpublic void testJoinWith() {// 使用 Mockito 框架模拟 StringUtils 类的静态方法MockedStaticStringUtils stringUtilsMockedStatic Mockito.mockStatic(StringUtils.class);// 创建一个字符串列表作为 joinWith 方法的输入参数ListString stringList Arrays.asList(a, b, c);// 配置模拟行为当调用 StringUtils.joinWith(,, stringList) 时返回 a,b,cstringUtilsMockedStatic.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 类的静态方法MockedStaticStringUtils stringUtilsMockedStatic Mockito.mockStatic(StringUtils.class);// 创建一个字符串列表作为 joinWith 方法的输入参数ListString stringList Arrays.asList(a, b, c);// 配置模拟行为当调用 StringUtils.joinWith(,, stringList) 时返回 a,b,cstringUtilsMockedStatic.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类的静态方法MockedStaticStringUtils stringUtilsMockedStatic Mockito.mockStatic(StringUtils.class);// 创建一个字符串列表作为join方法的输入ListString stringList Arrays.asList(a, b, c);// 配置模拟行为当调用StringUtils.join(,, stringList)时返回字符串a,b,cstringUtilsMockedStatic.when(() - StringUtils.join(,, stringList)).thenReturn(a,b,c);// 断言验证模拟行为是否正确即 join 方法返回的字符串是否与预期的 a,b,c 相等Assertions.assertTrue(StringUtils.join(,, stringList).equals(a,b,c));}}
然后执行整个测试类后会报错就会报错 原因是因为 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(测试后结果);}} 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;}}DataAllArgsConstructorNoArgsConstructorpublic static class BClass {private AClass aClass;public int add(int a, int b) {// 调用a方法int add aClass.add();System.out.println(测试了ab 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() 方法不同的是 被 spy 的对象会走真实的方法而 mock 对象不会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(测试了ab aa,bb);return ab;}}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 对象打桩因此返回默认值 使用Spy()注解示例。引入依赖
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.ucarinc.framework/groupIdartifactIddemo1/artifactIdversion1.0-SNAPSHOT/versionpackagingjar/packagingnamedemo1/nameurlhttp://maven.apache.org/urlpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/propertiesdependenciesdependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdversion2.0.13/version/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-log4j12/artifactIdversion2.0.13/version/dependencydependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter/artifactIdversion5.10.2/versionscopetest/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.32/version/dependencydependencygroupIdorg.mockito/groupIdartifactIdmockito-core/artifactIdversion5.11.0/versionscopetest/scope/dependencydependencygroupIdorg.mockito/groupIdartifactIdmockito-inline/artifactIdversion5.2.0/versionscopetest/scope/dependency!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.14.0/version/dependencydependencygroupIdorg.mockito/groupIdartifactIdmockito-junit-jupiter/artifactIdversion5.11.0/versionscopetest/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(测试了ab aa,bb);return ab;}}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);ArgumentCaptorString arg ArgumentCaptor.forClass(String.class);mockList.add(one);Mockito.verify(mockList).add(arg.capture());assertEquals(one, arg.getValue());
}使用Captor来创建一个ArgumentCaptor实例 MockListString mockedList;CaptorArgumentCaptorString 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),会自动处理MockSpyInjectMocks等注解
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结构 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, test1baomidou.com),(2, Jack, 20, test2baomidou.com),(3, Tom, 28, test3baomidou.com),(4, Sandy, 21, test4baomidou.com),(5, Billie, 24, test5baomidou.com);引入依赖
创建springboot 项目。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.3.1/versionrelativePath/ !-- lookup parent from repository --/parentmodelVersion4.0.0/modelVersiongroupIdcom.lkcoffee.framework/groupIdartifactIddemo2/artifactIdversion0.0.1-SNAPSHOT/versionnamedemo2/namedescriptiondemo2/descriptionpropertiesjava.version17/java.versionproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncodingspring-boot.version3.3.1/spring-boot.version/propertiesdependenciesdependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-spring-boot3-starter/artifactIdversion3.5.7/version/dependency!-- springbbot配置--dependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactIdscoperuntime/scopeversion8.3.0/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactId/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.28/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependenciesbuildpluginsplugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-compiler-plugin/artifactIdversion3.8.1/versionconfigurationsource17/sourcetarget17/targetencodingUTF-8/encoding/configuration/pluginplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdversion${spring-boot.version}/versionconfigurationmainClasscom.lkcoffee.framework.demo2.Demo2Application/mainClassskiptrue/skip/configurationexecutionsexecutionidrepackage/idgoalsgoalrepackage/goal/goals/execution/executions/plugin/plugins/build/project
添加application.yml
server:port: 8080spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8servlet: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?useUnicodetrueuseSSLfalsecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueusername: 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 BaseMapperUser {}编写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 IServiceUser {/*** 查询所有用户信息* return 所有用户信息*/ListUser 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 ServiceImplUserMapper, User implements UserService {Overridepublic ListUser 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 ListUser 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返回一下结果说明项目启动成功 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() {mockUserServiceMockito.mock(UserService.class);spyUserServiceMockito.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(调用了真实方法 测试了ab aa,bb);return ab;}}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;InjectMocksSpyprivate 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 1004) 多次打桩
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 ListInteger 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;ResourceSpyBeanprivate 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,203462009qq.com),new User(2L, Bob, 30,203462008qq.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,203462009qq.com));// 执行查询var result userService.queryById(1L);// 验证查询结果assertEquals(Alice, result.getName());}Testvoid testAddUser() {// 创建一个用户对象User user new User(null, Alice, 25,203462009qq.com);// 模拟save方法返回结果when(userMapper.insert(user)).thenReturn(1);// 执行添加用户var result userService.addUser(user);// 验证添加结果assertTrue(result);}
}3.3 Springboot测试注解
MockBean
MckBean是Spring Boot提供的注解专门用于在Spring应用上下文中添加或替换一个bean为mock对象。这个注解主要用于集成测试场景特别是当测试需要Spring环境支持时如测试控制器与服务层的交互等。 使用MockBean注解的的对象会生成一个Mock的bean.不会生成原来的bean并会将该bean注入到依赖该bean的其他bean中正常的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);}
}