Mockito详细教程

Mockito详细教程-CSDN博客


  • Mockito 详细教程-CSDN 博客
  • https://blog.csdn.net/qq_37855749/article/details/125362496
  • 2024-08-09 11:16:40

如果需要初始化一些值的变量,可以通过

@Beforepublic void setUp() {MockitoAnnotations.openMocks(this);ReflectionTestUtils.setField(ratePlanHandler, "sendHandler", sendHandler);ReflectionTestUtils.setField(ratePlanService, "mockOpen", 0);}

前言

单元测试(UT)
工作一段时间后,才真正意识到代码质量的重要性。虽然囫囵吞枣式地开发,表面上看来速度很快,但是给后续的维护与拓展制造了很多隐患。
作为一个想专业但还不专业的程序员,通过构建覆盖率比较高的单元测试用例,可以比较显著地提高代码质量。如后续需求变更、版本迭代时,重新跑一次单元测试即可校验自己的改动是否正确。

Mockito 和单元测试有什么关系?
与集成测试将系统作为一个整体测试不同,单元测试更应该专注于某个类。所以当被测试类与外部类有依赖的时候,尤其是与数据库相关的这种费时且有状态的类,很难做单元测试。但好在可以通过“Mockito”这种仿真框架来模拟这些比较费时的类,从而专注于测试某个类内部的逻辑。

SpringBoot 与 Mockito

spring-boot-starter-test 中已经加入了 Mockito 依赖,所以我们无需手动引入。
另外要注意一点,在 SpringBoot 环境下,我们可能会用 @SpringBootTest ​注解。

@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
@ExtendWith({ SpringExtension.class})
public @interface SpringBootTest { 

如果用这个注解,跑单元测试的时候会加载 SpringBoot 的上下文,初始化 Spring 容器一次,显得格外的慢,这可能也是很多人放弃在 Spring 环境下使用单元测试的原因之一。
不过我们可以不用这个 Spring 环境,单元测试的目的应该是只测试这一个函数的逻辑正确性,某些容器中的相关依赖可以通过 Mockito 仿真。

所以我们可以直接拓展自 MockitoExtendsion,这样跑测试就很快了。

@ExtendWith(MockitoExtension.class)
public class ListMockTest { 
}

基本使用

mock 与 verify

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class ListMockTest {

@Test
public void mockList() { List mockedList  = mock(List.class);mockedList.add("one");mockedList.clear();verify(mockedList).add("one");verify(mockedList).clear();
}

}

mock(List.class)会返回一个 List 的仿真对象,可以理解为“假对象”,要与后面提到的 spy 区分开。
通过 Mockito 的 verify 来验证是否调用过 List 的 add 方法。

stubbing(存根)

什么是存根

注意:mocking 和 stubbing 背后的理论很庞大。这里的解释只是针对于这个框架而言,比较粗浅。

上面通过 mock 函数得到了一个代理对象,调用这个对象的函数时,如果有返回值,默认情况下返回值都是 null,如果基本类型,默认值是 0 或者 false。

  @Testpublic void mockList() { List mockedList  = mock(List.class);
    System.out.println(mockedList.get(0));
}

控制台输出

null

当测试的单元依赖这个 mock 对象的返回值时,我们可以通过提前申明这个函数的返回值来测试各种各样的场景。
提前申明的这个过程被称为存根

@ExtendWith(MockitoExtension.class)
public class ListMockTest { 
@Test
public void mockList() { List mockedList  = mock(List.class);//调用get(0)时,返回firstwhen(mockedList.get(0)).thenReturn("first");//调用get(1)时,直接抛出异常when(mockedList.get(1)).thenThrow(new RuntimeException());//返回firstSystem.out.println(mockedList.get(0));//抛出异常System.out.println(mockedList.get(1));//没有存根,则会返回nullSystem.out.println(mockedList.get(999));
}

}

注意点

  • 存根时可以被覆盖的(即对一种情况多次存根的话,以最后一次为准),但是不鼓励这么做,可读性会变差。
  • 一旦存根后,这个函数会一直返回这个值,不管你调用多少次。

返回值为 void

即使有些函数返回值为 void,也可以使用存根。

//调用clear方法时,抛出异常
// 因为clear方法没有返回值,所以不能在后面接着写return,只能在前面些doThrow或者doReturn
doThrow(new RuntimeException()).when(mockedList).clear();

mockedList.clear();

连续存根

多次调用,返回不同的值。

    @Testpublic void mockList() { List mockedList  = mock(List.class);when(mockedList.get(0)).thenReturn(0).thenReturn(1).thenReturn(2);
    System.out.println(mockedList.get(0));System.out.println(mockedList.get(0));System.out.println(mockedList.get(0));
}

返回值:

0
1
2

也可以简化为下面的这种写法,效果一样。

        when(mockedList.get(0)).thenReturn(0, 1, 2);

设置回调函数

调用某个函数的时候,执行一个回调函数。

    @Testpublic void mockList() { List mockedList = mock(List.class);when(mockedList.get(anyInt())).thenAnswer(new Answer<Object>() { @Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable { System.out.println("哈哈哈,被我逮到了吧");Object[] arguments = invocationOnMock.getArguments();System.out.println("参数为:" + Arrays.toString(arguments));Method method = invocationOnMock.getMethod();System.out.println("方法名为:" + method.getName());
            return &quot;结果由我决定&quot;;}});System.out.println(mockedList.get(0));
}

控制台打印:

哈哈哈,被我逮到了吧
参数为:[0]
方法名为:get
结果由我决定

存根函数家族

除了上面出现的 doReturn​、doThrow​、doAnswer​ 外,还有:
doNothing() ​啥也不干
doCallRealMethod() ​调用真正的方法(不代理)

参数匹配器

基本用法

看完上面的存根,可能会有一个疑问:如果我想监控这个对象有没有被调用 get 方法,具体参数是什么我并不关心,该咋办。
这个时候就用到了参数匹配器。

    @Testpublic void mockList() { List mockedList  = mock(List.class);
    when(mockedList.get(0)).thenReturn(&quot;first&quot;);//返回firstSystem.out.println(mockedList.get(0));//验证是否调用过get函数。这里的anyInt()就是一个参数匹配器。verify(mockedList).get(anyInt());
}

处理 anyInt()​,还有很多的参数匹配器,默认的放在 ArgumentMatchers​ 类中。当然,也可以根据需求自定义参数匹配器或者使用 hamcrest​ 匹配器。
当一个函数接收多个参数时,如果其中有一个用了参数匹配器,那其他的参数也必须用。

    class Student{ public void sleep(int id, String studNo, String name) { 
    }
}@Test
public void mockStudent() { Student student = mock(Student.class);student.sleep(1, &quot;1&quot;, &quot;admin&quot;);verify(student).sleep(anyInt(), anyString(), eq(&quot;admin&quot;));verify(student).sleep(anyInt(), anyString(), eq(&quot;admin&quot;));
}

正确的用法是:

    @Testpublic void mockStudent() { Student student = mock(Student.class);
    student.sleep(1, &quot;1&quot;, &quot;admin&quot;);verify(student).sleep(anyInt(), anyString(), eq(&quot;admin&quot;));
}

ArgumentCaptor

当我们需要去验证函数外部的一些参数时,就需要用到这个。
以发送邮件为例
定义一个邮件类:

@Data
@NoArgsConstructor
public class Email { 
private String to;
private String subject;
private String body;
private EmailStyle emailStyle;public Email(String to, String subject, String body) { this.to = to;this.subject = subject;this.body = body;
}

}

邮件有以下两种样式

public enum EmailStyle { HTML,DOC;
}

邮件服务会调用邮件平台发送邮件

public class EmailService { 
private DeliveryPlatform deliveryPlatform;public EmailService(DeliveryPlatform deliveryPlatform) { this.deliveryPlatform = deliveryPlatform;
}public void send(String to, String subject, String body, boolean html) { EmailStyle emailStyle = EmailStyle.DOC;if(html) { emailStyle = EmailStyle.HTML;}Email email = new Email(to, subject, body);email.setEmailStyle(emailStyle);deliveryPlatform.deliver(email);
}

}

邮件平台代码如下:

public class DeliveryPlatform { 
public void deliver(Email email) { //do something
}

}

现在我想验证一个问题,当我发送 HTML 邮件时,deliver 这个函数收到的 email 到底是不是 HTML 类型的。
这种情况下,就可以通过 ArgumentCaptor 的方式来解决了。

@ExtendWith(MockitoExtension.class)
public class EmailServiceTest { 
@Mock
private DeliveryPlatform deliveryPlatform;@InjectMocks
private EmailService emailService;@Captor
private ArgumentCaptor&lt;Email&gt; emailArgumentCaptor;@Test
public void testHtmlEmail() { emailService.send(&quot;某人&quot;, &quot;无题&quot;, &quot;无内容&quot;, true);verify(deliveryPlatform).deliver(emailArgumentCaptor.capture());Email email = emailArgumentCaptor.getValue();Assertions.assertEquals(EmailStyle.HTML, email.getEmailStyle());
}

}

验证函数被调用的次数

下面的这个测试将不会通过

    @Testpublic void mockList() { List mockedList  = mock(List.class);
    when(mockedList.get(0)).thenReturn(&quot;first&quot;);//返回firstSystem.out.println(mockedList.get(0));System.out.println(mockedList.get(0));//验证是否被用过getverify(mockedList).get(anyInt());
}

报错如下:

org.mockito.exceptions.verification.TooManyActualInvocations: 
list.get(<any integer>);
Wanted 1 time:
-> at com.dayrain.mockitodemo.test.ListMockTest.mockList(ListMockTest.java:43)
But was 2 times:
-> at com.dayrain.mockitodemo.test.ListMockTest.mockList(ListMockTest.java:39)
-> at com.dayrain.mockitodemo.test.ListMockTest.mockList(ListMockTest.java:40)

大概意思是,只希望这个函数被调用一次,但实际上被调用了两次。
可能有点懵,不过点进 verify 方法后就明白了,默认情况下只调用一次;

public static <T> T verify(T mock) { return MOCKITO_CORE.verify(mock, times(1));
}

所以在调用的 verify 方法的时候,指定下调用次数即可。

verify(mockedList, times(2)).get(anyInt());

甚至支持不指定固定次数

 //一次也不能调用,等于times(0)verify(mockedList, never()).add("never happened");

//至多、至少
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");

创建 mock 对象的另一种方式:@Mock

上述方法都是通过 mock 方法来构建仿真对象的,其实更简单的方法是通过注解。

    @Mockprivate List mockedList;
@Test
public void mockList() { mockedList.add(&quot;one&quot;);verify(mockedList).add(&quot;one&quot;);
}

Spy(间谍)

介绍

上面讲的一些操作都是和 Mock 出来的对象相关的。通过 mock() ​或者 @Mcok​ 注解标注的对象,可以理解为“假对象”。

如果一个对象使用 @Mock​ 修饰,那么不会执行该对象的内部真实方法,都是统一直接返回默认值(0,null,false),或者返回我们使用 thenReturn​ 方法执行的返回结果,如果我们想要他能够进入这个对象的方法内部是进行实际执行,那么就使用 @Spy​​​ 注解即可

Spy 是针对于“真实存在”的对象。
在重构已有的旧代码时,Spy 会比较好用。

    @Testpublic void spyList() { //申请了一个真实的对象List list = new LinkedList();List spy = spy(list);
    //可以选择存根某些函数when(spy.size()).thenReturn(100);//调用真实的方法spy.add(&quot;one&quot;);spy.add(&quot;two&quot;);//打印第一个元素System.out.println(spy.get(0));//获取list的大小System.out.println(spy.size());//验证verify(spy).add(&quot;one&quot;);verify(spy).add(&quot;two&quot;);
}

当使用 spy 的时候,有一个很容易掉进去的陷进。即 spy 监听的是真实的对象,在操作真实对象的时候可能会出现越界之类的问题。

    @Testpublic void spyList() { List list = new LinkedList();List spy = spy(list);
    //报错 IndexOutOfBoundsException, 因为这个List还是emptywhen(spy.get(0)).thenReturn(&quot;foo&quot;);//通过doReturn(&quot;foo&quot;).when(spy).get(0);
}

注解

和 @Mock 类似,还可以用 @Spy 注解。

BDD(行为驱动开发)

针对比较流行的行为驱动开发,Mockito 也提供了对应的支持:
如 org.mockito.BDDMockito 类中的 given//when//then
BDD 本文就不做拓展了,后续有时间再做梳理。

超时验证

如果要验证执行是否超时,可以这么做:

verify(student, timeout(1).times(1)).sleep(anyInt(), anyString(), eq("admin"));

自动实例化 @InjectMocks

下面举一个比较常见的例子
已有用户类

@Data
public class UserInfo { private String name;private String password;
public UserInfo(String name, String password) { this.name = name;this.password = password;
}

}

有对应的服务以及数据存储接口

@Service
public class UserInfoService { 
@Autowired
private UserInfoDao userInfoDao;public void printInfo() { UserInfo userInfo = userInfoDao.select();System.out.println(userInfo);
}

}

public interface UserInfoDao {
UserInfo select();
}

如果我要测试这个 service,并且不想和数据库有交互,那么可以创建一个 UserInfoDao mock 对象。
被测试类标注为@InjectMocks时,会自动实例化,并且把 @Mock 或者 @Spy 标注过的依赖注入进去。

@ExtendWith(MockitoExtension.class)
public class UserInfoServiceTest { 
@InjectMocks
private UserInfoService userInfoService;@Mock
private UserInfoDao userInfoDao;@Test
public void testPrint() { UserInfo userInfo = new UserInfo(&quot;admin&quot;, &quot;123&quot;);when(userInfoDao.select()).thenReturn(userInfo);userInfoService.printInfo();
}

}

运行结果为:

UserInfo(name=admin, password=123)

参考

本文大部分内容来自于官网,但不会完全照搬,只整理我认为可能用得到的地方。并且可能会用自己的语言重新组织一下,或者替换部分示例代码,望谅解。
官网地址:https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
参考博客 1:https://semaphoreci.com/community/tutorials/stubbing-and-mocking-with-mockito-2-and-junit
参考博客 2:https://www.baeldung.com/mockito-argumentcaptor
如果您对其他语言的模拟也比较感兴趣,例如 python,可以学习下面的博客:
https://semaphoreci.com/community/tutorials/getting-started-with-mocking-in-python

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/894893.html

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

相关文章

主机、主机中的Ubuntu虚拟机、与ixm6ull开发板三者互ping且均能联网个人流程记录

1.0 设备连接要求笔记本通过网线连接到开发板,且笔记本通过 usb转type-c线连接到开发板的对应位置(USB TTL那个)。 笔记本需要连接WIFI。 笔记本、开发板、Ubuntu虚拟机启动且能够正常运行。2.0 打开WIFI的网络共享 控制面板 -> 网络和 Internet -> 网络和共享中心,选…

基于PID控制器的六自由度串联机器人控制系统的simulink建模与仿真

1.课题概述 基于PID控制器的六自由度串联机器人控制系统的simulink建模与仿真。2.系统仿真结果 (完整程序运行后无水印) 3.核心程序与模型 版本:MATLAB2022a 4.系统原理简介六自由度串联机器人控制系统是机器人学中的一个核心问题,其中PID控制器因其简单、实用和易于调整…

Windows快捷方式文件相对路径

前言全局说明Windows快捷方式相对路径 通常情况下创建快捷方式,使用的都是绝对路径,如果文件目录迁移到别的地方,不同路径下,那么这个快捷方式就失效了,如果使用相对路径,只要父文件夹不变,那么子文件夹中的快捷方式就能一直有效。一、说明 1.1 环境: Windows 11 家庭版…

基于GARCH-Copula-CVaR模型的金融系统性风险溢出效应matlab模拟仿真

1.程序功能描述 基于GARCH-Copula-CVaR模型的金融系统性风险溢出效应matlab模拟仿真,仿真输出计算违约点,资产价值波动率,信用溢价,信用溢价直方图等指标。 2.测试软件版本以及运行结果展示MATLAB2022A版本运行 (完整程序运行后无水印) 3.核心程序%计算违约点 DP …

Java笔记-17、Web后端基础 Java操作数据库

JDBCsun公司官方定义的一套操作所有关系型数据库的规范,即接口。 各个数据库厂商去实现这套接口,提供数据库驱动jar包。 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。public void testUpdate() throws Exception {// 注册驱动Class.forName(&qu…

netcore后台服务慎用BackgroundService

在 .NET Core 开发中,BackgroundService 是一个非常方便的后台任务运行方式,但它并不适用于所有场景。 BackgroundService 一时爽,并发火葬场。 BackgroundService 适用于单实例的无状态后台任务,例如:定期清理任务(删除过期数据、日志清理) 轻量级定时任务(如定期检查…

基于遗传优化SVM的电机参数预测matlab仿真

1.算法运行效果图预览 (完整程序运行后无水印)输入:电机结构参数x1 x2 x3 x4 x5(分别是铁心高度 铁心厚度 绕组匝数 窗口宽度 导线截面积 ) 目标值:体积v、加速度ax、加速度ay和加速度az 2.算法运行软件版本 matlab2022a3.部分核心程序 (完整版代码包含详细中文注释和操作…

JetBrains Rider 2024软件下载与安装教程

Rider2024是一款基于IntelliJ以及ReSharper所开发的跨平台式的开发环境,并且该软件也是C#、Unity等应用程序的专属开发环境。提供了极为强大的代码编辑器,对于C#和Unity等都能完美兼容,开发者用户们能够在其中轻松自在的编写出代码项目,同时还提供了智能代码补全的功能,提…

JetBrains CLion 2024软件下载与安装教程

1、安装包 扫描下方二维码关注「软知社」,后台回复【046】三位数字即可免费获取分享链接,无广告拒绝套路;2、安装教程(建议关闭杀毒软件)解压下载安装包文件,双击exe安装,弹窗安装对话框点击下一步选择软件的安装路径,选择C盘之外的空间,点击下一步创建桌面快捷方式勾选…

2025.3.6 起步

今天学习了web安全的基本知识 1,http,一种协议,常用TCP 2,http的请求方法(GET/POST/PUT...)和请求状态(200 OK/404 NOT FOUND...) 3,URL网址,及其组成 4,UA头,User-Agent,可以知道操作系统、CPU、浏览器类型 5,BurpSuite抓包返回包,可以得到很多信息6,Referer,告诉…

《AI时代生存手册:零基础掌握DeepSeek》 - PDF免费下载

通过本书,你将轻松上手DeepSeek,开启智能生活新篇章。通过本书,你将学会用Deepseek大幅提升工作效率,告别烦琐,拥抱高效。通过本书,你将学会如何让Deepseek成为您的职场超级助手。通过本书,你将学会如何利用DeepSeek激发自己的创作灵感,打造爆款内容,打造个人品牌。通…

Hive安装--本地模式

系统版本:CentOS Linux release 7.9.2009 (Core)ps: 最小化安装一、安装MySQL 1.下载 1.1安装包 官网:https://downloads.mysql.com/archives/community/1.2驱动 官网:https://downloads.mysql.com/archives/c-j/ps mysql-connector-java-5.1.47.jar,要这个2.安装 2.1安装依…