文章目录
单元测试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项目。添加依赖
<projectxmlns="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对象。下面是它的方法接口
publicstatic<T>Tmock(Class<T> classToMock)
- classToMock:待 mock 对象的 class 类。
- 返回 mock 出来的类
实例:使用 mock 方法 mock 一个类
importorg.junit.Assert;importorg.junit.Test;importjava.util.List;importstaticorg.mockito.Mockito.*;publicclassMyTest{@TestpublicvoidmyTest(){/* 创建 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() 代码示例
publicvoidtest02(){// 模拟random对象,这个对象是假的Random random =Mockito.mock(Random.class);// 当调用了random对象时,返回100这个值Mockito.when(random.nextInt()).thenReturn(100);// 验证,应该是对的。有人会问,random.nextInt()不是获取随机值吗?// 现在这个random对象是假的Assertions.assertEquals(100, random.nextInt());}
完整的另一个demo
packagecom.ucarinc.framework;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importstaticorg.mockito.Mockito.mock;importstaticorg.mockito.Mockito.times;importstaticorg.mockito.Mockito.verify;importstaticorg.mockito.Mockito.when;publicclassApp5Test{privatefinalLogger log=LoggerFactory.getLogger(App5Test.class);publicstaticclassMockitoTestController{publicintadd(int a,int b){System.out.println("测试了a+b a="+a+",b="+b);return a+b;}}@TestvoidtestAdd(){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异常的情况。
* 该测试函数不接受参数,也没有返回值。
*/@TestvoidtestAddException(){TestController mockitoTestController =Mockito.mock(TestController.class);// 设置mock对象,在调用mockitoTestController的add方法时抛出RuntimeException异常when(mockitoTestController.add(1,2)).thenThrow(newRuntimeException("add error"));// 验证是否抛出了RuntimeException异常Assertions.assertThrows(RuntimeException.class,()-> mockitoTestController.add(1,2));}publicstaticclassTestController{publicintadd(int a,int b){System.out.println("测试了a+b="+a+",b="+b);return a+b;}}
有种特殊情况,就是void返回值打桩
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.service.UserService;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.MockitoAnnotations;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importjava.util.List;importstaticorg.mockito.Mockito.doNothing;importstaticorg.mockito.Mockito.verify;@ExtendWith(MockitoExtension.class)publicclassTest4{@MockList<String> mockList;@Testpublicvoidtest1(){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 对象方法的返回值为返回类型的默认值。
packagecom.ucarinc.framework;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.mockito.Mockito;importjava.util.Random;importstaticorg.mockito.Mockito.times;importstaticorg.mockito.Mockito.verify;publicclassAppTest{@Testpublicvoidtest01(){// 使用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)用于更灵活的验证和配置行为:
importstaticorg.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静态方法的所属类,此方法返回一个具有作用域的模拟对象。
@TestpublicvoidtestJoinWith(){// 使用 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"));}
但是如果你写成下面这样子的话,会发送报错
packagecom.ucarinc.framework;importorg.apache.commons.lang3.StringUtils;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.mockito.MockedStatic;importorg.mockito.Mockito;importjava.util.Arrays;importjava.util.List;classDemo2ApplicationTests{@TestpublicvoidtestJoinWith(){// 使用 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方法是否按照预期工作。
* */@TestpublicvoidtestJoin(){// 使用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"));}}
然后执行整个测试类后会报错:,就会报错
原因是因为 mockStatic() 方法是将当前需要 mock 的类注册到本地线程上(ThreadLocal),而这个注册在一次 mock 使用完之后是不会消失的,需要我们手动的去销毁。如过没有销毁,再次 mock 这个类的时候 Mockito 将会提示我们 :”当前对象 mock 的对象已经在线程中注册了,请先撤销注册后再试“。这样做的目的也是为了保证模拟出来的对象之间是相互隔离的,保证同时和连续的测试不会收到上下文的影响。
2.2 常用注解
1) @Mock
快速
mock
的方法,使用
@mock
注解。
*mock 注解需要搭配
MockitoAnnotations.openMocks(testClass)
方法一起使用。*
packagecom.ucarinc.framework;importorg.apache.commons.lang3.StringUtils;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.mockito.Mock;importorg.mockito.MockedStatic;importorg.mockito.Mockito;importorg.mockito.MockitoAnnotations;importjava.util.Arrays;importjava.util.List;importjava.util.Random;importstaticorg.mockito.Mockito.*;publicclassApp2Test{@MockprivateRandom random;@BeforeEachvoidsetUp(){MockitoAnnotations.openMocks(this);}/**
* 测试Mockito框架的使用,模拟Random类的nextInt方法。
* 该测试函数没有参数和返回值,主要用于演示Mockito的基本用法。
*/@Testpublicvoidtest02(){// 调用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
packagecom.ucarinc.framework;importlombok.extern.slf4j.Slf4j;importorg.junit.jupiter.api.AfterEach;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.MockitoAnnotations;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.Random;importstaticorg.mockito.Mockito.times;importstaticorg.mockito.Mockito.verify;publicclassRandomTest02{privatefinalLogger log=LoggerFactory.getLogger(RandomTest02.class);@MockprivateRandom random;@BeforeEachvoidsetUp(){
log.info("==============测试前准备===============");MockitoAnnotations.openMocks(this);}/**
* 测试Mockito框架的使用,模拟Random类的nextInt方法。
* 该测试函数没有参数和返回值,主要用于演示Mockito的基本用法。
*/@Testpublicvoidtest02(){// 调用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();}@AfterEachvoidtearDown(){
log.info("==============测试后结果===============");}}
3) @InjectMocks
@InjectMocks
用于将模拟对象注入到被测试类中的相应字段。通过该注解可以自动将模拟对象注入到被测试类中标记为@InjectMocks的字段中,可以理解为使用@Mock创建出来的对象注入到@InjectMocks创建的对象中,这样被测试类就可以使用模拟对象作为其依赖了。
packagecom.ucarinc.framework;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.mockito.ArgumentCaptor;importorg.mockito.Captor;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.ArrayList;importjava.util.List;importstaticorg.junit.jupiter.api.Assertions.assertEquals;importstaticorg.mockito.Mockito.mock;importstaticorg.mockito.Mockito.times;importstaticorg.mockito.Mockito.verify;importstaticorg.mockito.Mockito.when;publicclassApp6Test{@MockAClass aClass;@InjectMocksBClass bClass;@BeforeEachvoidsetUp(){MockitoAnnotations.openMocks(this);}@TestvoidtestAdd(){// 当调用a方法时,直接返回1000。a是模拟的when(aClass.add()).thenReturn(1000);Assertions.assertEquals(1003, bClass.add(1,2));}publicstaticclassAClass{publicAClass(){}publicintadd(){System.out.println("AClass.add");return1;}}@Data@AllArgsConstructor@NoArgsConstructorpublicstaticclassBClass{privateAClass aClass;publicintadd(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层注入其中
@InjectMocksprivateUserService userService;@MockBeanprivateUserMapper userMapper;
4) @Spy
spy()
方法与
mock()
方法不同的是
- 被
spy
的对象会走真实的方法,而mock
对象不会spy()
方法的参数是对象实例,mock
的参数是 class
首先,我们使用mock方法。做一个测试
packagecom.ucarinc.framework;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.mockito.Mockito;importjava.util.Random;importstaticorg.mockito.Mockito.*;publicclassApp3Test{publicstaticclassMockitoTestController{publicintadd(int a,int b){System.out.println("测试了a+b a="+a+",b="+b);return a+b;}}@Testpublicvoidtest01(){MockitoTestController mockitoTestController =newMockitoTestController();// 调用实际的 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()注解示例。引入依赖
<projectxmlns="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>
代码测试
packagecom.ucarinc.framework;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mockito;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.junit.jupiter.api.AfterEach;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importstaticorg.mockito.Mockito.when;importstaticorg.mockito.Mockito.times;importstaticorg.mockito.Mockito.verify;@ExtendWith(MockitoExtension.class)publicclassApp4Test{privatefinalLogger log=LoggerFactory.getLogger(App4Test.class);publicstaticclassMockitoTestController{publicintadd(int a,int b){System.out.println("测试了a+b a="+a+",b="+b);return a+b;}}@SpyprivateMockitoTestController mockitoTestController;@BeforeEachvoidsetUp(){}/**
* 测试add方法
* 该方法模拟调用mockitoTestController的add方法,传入参数1和2,期望返回值为3。
* 首先,通过when语句设置mockitoTestController的add方法返回值为3;
* 然后,使用assertThat断言验证调用add方法(1, 2)实际返回值确实为3;
* 最后,通过verify语句确认mockitoTestController的add方法确实被调用了一次,并传入了参数1和2。
*/@TestvoidtestAdd(){// 设置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:
@TestpublicvoidwhenNotUseCaptorAnnotation_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;@BeforeEachvoidsetUp(){MockitoAnnotations.openMocks(this);}@TestpublicvoidwhenUseCaptorAnnotation_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)publicclassSystemInfoServiceImplTest{@AutowiredprivateISystemInfoService systemInfoservice;@Testpublicvoidadd()throwsException{}@TestpublicvoidfindAll()throwsException{}}
@ExtendWith
@ExtendWith 具体Demo展示如下:
importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.BeforeTestExecutionCallback;importorg.junit.jupiter.api.extension.ExtendWith;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.extension.Extension;importorg.junit.jupiter.api.extension.ExtensionContext;// 定义一个自定义的JUnit扩展,用于在测试开始前输出日志classCustomExtensionimplementsBeforeTestExecutionCallback{@OverridepublicvoidbeforeTestExecution(ExtensionContext context){System.out.println("Before Test Execution");}}// 使用@ExtendWith注解加载自定义扩展@ExtendWith(CustomExtension.class)publicclass test {@Testvoidtest1(){System.out.println("Test 1");Assertions.assertTrue(true);}@Testvoidtest2(){System.out.println("Test 2");Assertions.assertEquals(2,1+1);}}
Mockito通常与JUnit结合使用,特别是JUnit 5,利用
@ExtendWith(MockitoExtension.class)
简化Mock对象的初始化
启动类加上@ExtendWith(MockitoExtension.class),会自动处理
@Mock
,
@Spy
,
@InjectMocks
等注解
importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.junit.jupiter.MockitoExtension;@ExtendWith(MockitoExtension.class)publicclassUserServiceTest{// 测试代码}
2.3 常见区别
Mock对象和Spy对象区别
方法插桩方法不插桩作用对象最佳实践mock对象执行插桩逻辑返回mock对象的默认值类、接口被测试类或其依赖spy对象执行插桩逻辑调用真实方法类、接口被测试类
3. Springboot 使用
首先看下完整的pom结构
3.1 数据准备
创建sql
createdatabaseifnotexists mockito;use mockito;DROPTABLEIFEXISTS`user`;CREATETABLE`user`(
id BIGINTNOTNULLCOMMENT'主键ID',
name VARCHAR(30)NULLDEFAULTNULLCOMMENT'姓名',
age INTNULLDEFAULTNULLCOMMENT'年龄',
email VARCHAR(50)NULLDEFAULTNULLCOMMENT'邮箱',PRIMARYKEY(id));INSERTINTO`user`(id, name, age, email)VALUES(1,'Jone',18,'[email protected]'),(2,'Jack',20,'[email protected]'),(3,'Tom',28,'[email protected]'),(4,'Sandy',21,'[email protected]'),(5,'Billie',24,'[email protected]');
引入依赖
创建springboot 项目。
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="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:8080
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone:GMT+8
servlet:
multipart:
max-file-size:1024MB
max-request-size:1024MB
application:
name: demo2
datasource:
driver-class-name:com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mockito?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
mybatis-plus:
global-config:
db-config:
logic-delete-field: isDelete
logic-delete-value:1
logic-not-delete-value:0
mapper-locations: classpath*:mapper/**/*Mapper.xml
configuration:
log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
logging:
file:
name: test.log
level:
root:INFO
org:
springframework:DEBUG
example:
springboottest:DEBUG
在Springboot 启动类中添加
@MapperScan
注解,扫描 Mapper 文件夹:
packagecom.lkcoffee.framework.demo2;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan("com.lkcoffee.framework.demo2.mapper")@SpringBootApplicationpublicclassDemo2Application{publicstaticvoidmain(String[] args){SpringApplication.run(Demo2Application.class, args);}}
编写实体类
importlombok.Data;@DatapublicclassUser{privateLong id;privateString name;privateInteger age;privateString email;}
编写 Mapper 接口类
UserMapper.java
:
importorg.springframework.stereotype.Repository;@RepositorypublicinterfaceUserMapperextendsBaseMapper<User>{}
编写Service层
packagecom.lkcoffee.framework.demo2.service;importcom.baomidou.mybatisplus.extension.service.IService;importcom.lkcoffee.framework.demo2.domain.User;importjava.util.List;/**
* @Desciption: 用户服务层
* @Author: feixiang.li
* @date: 2024-07-11 19:51
**/publicinterfaceUserServiceextendsIService<User>{/**
* 查询所有用户信息
* @return 所有用户信息
*/List<User>queryAll();/**
* 根据用户id查询
* @param id 用户id
* @return 用户信息
*/UserqueryById(Long id);/**
* 添加用户id
* @param user 用户信息
* @return 操作结果
*/BooleanaddUser(User user);/**
* 根据用户id修改用户信息
* @param user
* @return
*/IntegerupdateUser(User user);}
实现Service层
packagecom.lkcoffee.framework.demo2.service.impl;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.lkcoffee.framework.demo2.domain.User;importcom.lkcoffee.framework.demo2.mapper.UserMapper;importcom.lkcoffee.framework.demo2.service.UserService;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.lang3.StringUtils;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importjava.util.List;importjava.util.Objects;/**
* @Desciption: 用户操作类
* @Author: feixiang.li
* @date: 2024-07-12 10:39
**/@Slf4j@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{@OverridepublicList<User>queryAll(){
log.info("被真实调用了, 执行了 查询所有用户信息");returnlist();}@OverridepublicUserqueryById(Long id){
log.info("被真实调用了, 根据用户id:{} 查询用户",id);returngetById(id);}@Transactional(rollbackFor =Exception.class)@OverridepublicBooleanaddUser(User user){
log.info("被真实调用了, 添加用户信息:{}",user);if(Objects.nonNull(user.getId())){thrownewRuntimeException("被真实调用了,新增用户,id应该为空");}if(Objects.isNull(user.getAge())|| user.getAge()<0|| user.getAge()>100){thrownewRuntimeException("被真实调用了,请填写正确的年龄");}if(StringUtils.isBlank(user.getName())){thrownewRuntimeException("被真实调用了,对不起,姓名不能为空");}returnsave(user);}@Transactional(rollbackFor =Exception.class)@OverridepublicIntegerupdateUser(User user){System.out.println("执行了真实的更新用户方法");int result=getBaseMapper().updateById(user);System.out.println("update user result:"+result);return result;}}
编写controller
packagecom.lkcoffee.framework.demo2.controller;importcom.lkcoffee.framework.demo2.domain.User;importcom.lkcoffee.framework.demo2.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.util.Assert;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.List;importjava.util.Objects;importjava.util.Optional;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 10:45
**/@RestController@RequestMapping("/user")publicclassUserController{@AutowiredprivateUserService userService;@GetMappingpublicList<User>queryAll(){return userService.queryAll();}@GetMapping("/{id}")publicUserqueryById(@PathVariableLong id){if(Objects.isNull(id)){returnnewUser();}return userService.queryById(id);}@PostMappingpublicStringsave(@RequestBodyUser 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等注解
方法一
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.service.UserService;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;@ExtendWith(MockitoExtension.class)publicclassTest1{@MockprivateUserService mockUserService;@SpyprivateUserService spyUserService;@Testpublicvoidtest1(){// 判断某个对象是不是mock对象System.out.println("Mockito.mockingDetails(mockUserService).isMock(): "+Mockito.mockingDetails(mockUserService).isMock());System.out.println("Mockito.mockingDetails(spyUserService).isSpy(): "+Mockito.mockingDetails(spyUserService).isSpy());}}
方法二
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.service.UserService;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;publicclassTest2{@MockprivateUserService mockUserService;@SpyprivateUserService spyUserService;@BeforeEachpublicvoidinit(){
mockUserService=Mockito.mock(UserService.class);
spyUserService=Mockito.spy(UserService.class);}@Testpublicvoidtest1(){// 判断某个对象是不是mock对象System.out.println("Mockito.mockingDetails(mockUserService).isMock(): "+Mockito.mockingDetails(mockUserService).isMock());System.out.println("Mockito.mockingDetails(spyUserService).isSpy(): "+Mockito.mockingDetails(spyUserService).isSpy());}}
方法三
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.service.UserService;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.MockitoAnnotations;importorg.mockito.Spy;publicclassTest3{@MockprivateUserService mockUserService;@SpyprivateUserService spyUserService;@BeforeEachpublicvoidinit(){MockitoAnnotations.openMocks(this);}@Testpublicvoidtest1(){// 判断某个对象是不是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) 参数匹配
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.domain.User;importcom.lkcoffee.framework.demo2.service.UserService;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.Mockito.doReturn;/**
* 参数匹配:通过方法签名(参数)来制定哪些方法调用需要被处理
*/@ExtendWith(MockitoExtension.class)publicclassParamMatcherTest{privatefinalLogger log =LoggerFactory.getLogger(ParamMatcherTest.class);@MockprivateUserService mockUserService;@SpyprivateUserService spyUserService;@Testpublicvoidtest2(){/**
* 这里返回值是null. Mock对象不会调用真实方法
*/User user =newUser();
user.setId(1L);
user.setName("fly");doReturn(99).when(mockUserService).updateUser(user);int result1 = mockUserService.updateUser(user);
log.info("用户1修改对象返回值:{}", result1);User user2 =newUser();
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);}@Testpublicvoidtest1(){/**
* 这里返回值是null. Mock对象不会调用真实方法。如果不进行插桩的话
*/User user = mockUserService.queryById(1L);
log.info("user:{}", user);}}
3) 打桩
packagecom.lkcoffee.framework.demo2;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mockito;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.junit.jupiter.api.AfterEach;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.ArgumentMatchers.anyInt;importstaticorg.mockito.Mockito.doReturn;importstaticorg.mockito.Mockito.when;importstaticorg.mockito.Mockito.times;importstaticorg.mockito.Mockito.verify;@ExtendWith(MockitoExtension.class)publicclassApp4Test{privatefinalLogger log=LoggerFactory.getLogger(App4Test.class);publicstaticclassMockitoTestController{publicintadd(int a,int b){System.out.println("调用了真实方法 测试了a+b a="+a+",b="+b);return a+b;}}@SpyprivateMockitoTestController spyMockitoTestController;@BeforeEachvoidsetUp(){}/**
* 测试add方法
* 该方法模拟调用mockitoTestController的add方法,传入参数1和2,期望返回值为3。
* 首先,通过when语句设置mockitoTestController的add方法返回值为3;
* 然后,使用assertThat断言验证调用add方法(1, 2)实际返回值确实为3;
* 最后,通过verify语句确认mockitoTestController的add方法确实被调用了一次,并传入了参数1和2。
*/@TestvoidtestAdd(){// 设置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
注解
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.domain.User;importcom.lkcoffee.framework.demo2.mapper.UserMapper;importcom.lkcoffee.framework.demo2.service.UserService;importcom.lkcoffee.framework.demo2.service.impl.UserServiceImpl;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.Mockito;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.mock.mockito.MockBean;importorg.springframework.boot.test.mock.mockito.SpyBean;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.Mockito.doReturn;importstaticorg.mockito.Mockito.when;@ExtendWith(MockitoExtension.class)publicclassTest5{@MockprivateUserMapper userMapper;@MockprivateUserServiceImpl mockUserService;@InjectMocks@SpyprivateUserServiceImpl spyUserService;@Testpublicvoidtest1(){// 这一步是为了解决mybatisplus的问题。手动把mapper注入进去。// 如果使用了Autowired 的 Resource ,就不需要这一步了doReturn(userMapper).when(spyUserService).getBaseMapper();User user =newUser();
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) 多次打桩
packagecom.lkcoffee.framework.demo2;/**
* @Desciption:
* @Author: feixiang.li
* @date: 2024-07-12 14:38
**/importcom.lkcoffee.framework.demo2.domain.User;importcom.lkcoffee.framework.demo2.mapper.UserMapper;importcom.lkcoffee.framework.demo2.service.impl.UserServiceImpl;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.Mock;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.mock.mockito.MockBean;importjava.util.List;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.Mockito.doReturn;importstaticorg.mockito.Mockito.when;@ExtendWith(MockitoExtension.class)publicclassTest6{@MockprivateList<Integer> mockList;@Testpublicvoidtest1(){//第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) 实战
packagecom.lkcoffee.framework.demo2;importcom.lkcoffee.framework.demo2.domain.User;importcom.lkcoffee.framework.demo2.mapper.UserMapper;importcom.lkcoffee.framework.demo2.service.UserService;importcom.lkcoffee.framework.demo2.service.impl.UserServiceImpl;importjakarta.annotation.Resource;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.junit.jupiter.api.extension.ExtendWith;importorg.mockito.InjectMocks;importorg.mockito.Mock;importorg.mockito.MockitoAnnotations;importorg.mockito.Spy;importorg.mockito.junit.jupiter.MockitoExtension;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.mock.mockito.MockBean;importorg.springframework.boot.test.mock.mockito.SpyBean;importjava.util.List;importstaticorg.junit.jupiter.api.Assertions.assertEquals;importstaticorg.junit.jupiter.api.Assertions.assertTrue;importstaticorg.mockito.ArgumentMatchers.any;importstaticorg.mockito.Mockito.doReturn;importstaticorg.mockito.Mockito.when;@SpringBootTest(classes =Demo2Application.class)classUserServiceImplTest{@MockBeanprivateUserMapper userMapper;@Resource@SpyBeanprivateUserServiceImpl userService;@BeforeEachvoidsetUp(){// 这一步是为了解决mybatisplus 中没有baseMapper的问题// 因为是继承了ServiceImpl 。是父类。InjectMocks无法注入父类的属性// 如果使用了Autowired 的 Resource ,就不需要这一步了// doReturn(userMapper).when(userService).getBaseMapper();}@TestvoidtestQueryAll(){// 模拟查询结果when(userMapper.selectList(any())).thenReturn(List.of(newUser(1L,"Alice",25,"[email protected]"),newUser(2L,"Bob",30,"[email protected]")));// 执行查询var result = userService.queryAll();// 验证查询结果assertEquals(2, result.size());assertEquals("Alice", result.get(0).getName());assertEquals("Bob", result.get(1).getName());}@TestvoidtestQueryById(){// 模拟查询结果when(userMapper.selectById(1L)).thenReturn(newUser(1L,"Alice",25,"[email protected]"));// 执行查询var result = userService.queryById(1L);// 验证查询结果assertEquals("Alice", result.getName());}@TestvoidtestAddUser(){// 创建一个用户对象User user =newUser(null,"Alice",25,"[email protected]");// 模拟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)publicclassAbstractTestCase{}/**
* 1。使用@MockBean注解的的对象,会生成一个Mock的bean.不会生成原来的bean
* 2。并会将该bean注入到依赖该bean的其他bean中
* 3。正常的bean还是会正常组装注入
*/publicclassHelloControllerMockBeanTestextendsAbstractTestCase{@AutowiredprivateHelloController helloController;@MockBeanprivateHelloService helloService;@TestpublicvoidtestHello(){System.out.println("============only junit5================");
helloController.hello();System.out.println("============only junit5================");}}/**
* 1。使用@MockBean注解的的对象,会生成一个spy的bean行为与原类型一致.不会生成原来的bean
* 2。并会将该bean注入到依赖该bean的其他bean中
* 3。正常的bean还是会正常组装注入
*/publicclassHelloControllerSpyBeanTestextendsAbstractTestCase{@AutowiredprivateHelloController helloController;@SpyBeanprivateHelloService helloService;@TestpublicvoidtestHello(){System.out.println("============only junit5================");
helloController.hello();System.out.println("============only junit5================");}}
版权归原作者 說詤榢 所有, 如有侵权,请联系我们删除。