Mockito 入门

news2024/11/13 20:48:39

在这里插入图片描述

目录

    • 1.什么是 Mock 测试?
    • 2.Mockito简介
    • 3.在 SpringBoot 单元测试中使用 Mockito
      • 3.1 Maven依赖:
      • 3.2 UserService.java
      • 3.3 User.java
      • 3.4 thenReturn系列方法(测试桩)
      • 3.5 thenThrow系列方法
      • 3.6 verify 系列方法
    • 4.Spring中mock任何容器内对象
      • 4.1 Spring中正常使用Mockito
      • 4.2 Spring中mock任何容器内的对象
    • 5.Mockito的局限性
    • 6.总结

Mockito: 是一种 Java Mock 框架,主要用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等。

  • GitHub: https://github.com/mockito/mockito
  • 官网地址: https://site.mockito.org/
  • 官方文档: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
  • 中文文档: https://github.com/hehonghui/mockito-doc-zh#0

1.什么是 Mock 测试?

Mock 测试就是在测试过程中,创建一个假的对象,避免你为了测试一个方法,却要自行构建升格 Bean 的依赖链。

像下面这张图,类 A 需要调用类 B 和类 C有需要调用其他类如 D、E、F 等,假设类 D 是一个外部服务,那就会很难测,因为你的返回结果会直接的受外部服务影响,导致你的单元测试可能今天会过,但明天就过不了了。

而当我们引入 Mock 测试时,就可以创建一个假的对象,替换掉真实的 Bean B 和 C,这样在调用 B、C 的方法时,实际上就会去调用这个假的 Mock 对象的方法,而我们就可以自己设定这个 Mock 对象的参数和期望结果,让我们可以专注地测试当前的类 A,而不会受到其他的外部服务影响,这样测试效率就能提高很多。

2.Mockito简介

Mockito 是一种 Java Mock 框架,他主要用来做 Mock 测试的,它可以:

  • 模拟任何 Spring 管理的 Bean;
  • 模拟方法的返回值;
  • 模拟抛出异常等等;
  • 记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 Mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用。

比如 Mockito 可以在单元测试中模拟一个 Service 返回的参数,而不会真正去调用该 Service,这就是上面提到的 Mock 测试精神,也就是通过模拟一个假的 Service 对象,来快速的测试当前我想要测试的类。

目前在 Java 中主流的 Mock 测试工具有 Mockito、JMock、EasyMock等等,而 SpringBoot 目前内建的是 Mockito 框架。

题外话:Mockito 是命名自一种调酒莫吉托(Mojito),外国人也爱玩谐音梗…

3.在 SpringBoot 单元测试中使用 Mockito

3.1 Maven依赖:

spring-boot-starter-test 依赖中包含了:junit、spring-test、mockito

<!-- starter-test:junit + spring-test + mockito -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

3.2 UserService.java

@Component
public class UserService {
    @Autowired
    private UserDao userDao;
    
    /** 根据 ID 查询 用户信息 */
    public User getUserById(Integer id) {
        return userDao.getUserById(id);
    }
    
    /** 插入 用户信息 */
    public Integer insertUser(User user) {
        return userDao.insertUser(user);
    }
}

3.3 User.java

public class User {
    
    private Integer id;
    
    private String name;
    
    // 省略 getter/setter
}

如果这时候沃恩先不使用 Mockito 模拟一个假的 userDao 的 Bean,而是真的去调用一个正常的 Spring Bean 的 userDao 的话,其实就是很普通的注入 userService Bean,然后去调用它的方法,而它会再去调用 userDao 取得数据库的数据,然后我们再对返回结果做 Assert 断言检查:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
    // 先普通地注入一个 userService Bean
    @Autowired
    private UserService userService;
    
    @Test
    public void getUserById() throws Exception {
        // 普通地使用 userService,它里面会再去调用 userDao 取得数据库的数据
        User user = userService.getUserById(1);
        // 检查结果
        Assert.asserNotNull(user);
        Assert.assertEquals(user.getId(), new Integer(1));
        Assert.assertEquals(user.getName(), "John");
    }
}

如果 userDao 还没写好,又想先测 userService 的话,就需要使用 Mockito 去模糊一个假的 userDao 出来。

使用方法是在 userDao 上加上一个 @MockBean 的注解,当 userDao 被加上这个注解之后,表示 Mockito 会帮我们创建一个假的 Mock 对象,替换掉 Spring 中已存在的那个真实的 userDao Bean,也就是说,注入进 userService 的 userDao Bean,已经被我们替换成假的 Mock 对象了,所以当我们再次调用 userService 的方法时,会去调用的实际上是 mock userDao Bean 的方法,而不是真正的 userDao Bean。

当我们创建了一个假的 userDao 后,我们需要为这个 mock userDao 自定义方法的返回值,这里有一个公式用法,下面这段代码的意思是:当调用了某个 Mock 对象的方法时,就回传我们想要的自定义结果。

Mockito.when( 对象.方法名 ).thenReturn( 自定义结果 )

使用 Mockito 模拟 Bean 的单元测试具体实例如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @MockBean
    private UserDao userDao;
    
    @Test
    public void getUserById() throws Exception {
		// 定义当调用 mock userDao 的 getUserById方法,并且参数为 3 时,就返回 id 为 200、name 为 "I'm mock 3" 的 User 对象。
		Mockito.when(userDao.getUserById(3)).thenReturn(new User(200, "I'm mock 3"));
		// 返回的会是名字为 "I'm mock 3" 的 User 对象。
        User user = userService.getUserById(1);
        Assert.assertNotNull(user);
        Assert.assertEquals(user.getId(), new Integer(200));
        Assert.assertEquals(user.getName(), "I'm mock 3");
    }
}

3.4 thenReturn系列方法(测试桩)

测试桩: 通过 thenReturn 系列方法指定好返回值的这种操作也叫设置测试桩。通过测试桩来模拟底层模块的响应。

1)当使用任何数值调用 userService 的 getUserById 方法时,就回传一个名字为 “I’m mock” 的 User 对象。

Mockito.when(userService.getUserById(Mokito.anyInt)).thenReturn(new User(3, "I'm mock"));
// 回传的 user 的名字为 "I'm mock"
User user1 = userService.getUserById(3);
// 回传的 user 的名字也为 "I'm mock"
User user2 = userService.getUserById(200);

2)限制只有当参数的数字是 3 时,才会回传名字为 “I’m mock” 的 User 对象。

Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm mock"));
// 回传的 user 的名字为 "I'm mock"
User user1 = userService.getUserById(3);
// 回传的 user 为 null
User user2 = userService.getUserById(200);

3)当调用 userService 的 insertUser 方法时,不管传进来的 user 是什么,都回传 100.

Mockito.when(userService.insertUser(Mockito.an(User.class))).thenReturn(100);
// 会返回 100
Integer i = userService.insertUser(newUser);

3.5 thenThrow系列方法

1)当调用 userService 的 getUserById 的参数是 9 时,抛出一个 RuntimException。

Mockito.when(userService.getUserById(9)).thenThrow(new RuntimeException("mock throw exception"));
// 会抛出一个 RuntimException
User user = userService.getUserById(9);

2)如果方法没有返回值的话(即是方法定义为 public void myMethod {…}),要改用 doThrow 抛出 Exception。

Mockito.doThrow(new RuntimException("mock throw exception")).when(userService).print;
// 会抛出一个 RuntimeException
userService.print;

3.6 verify 系列方法

1)检查调用 userService 的 getUserById 方法,且参数为 3 的次数是否为 1 次。

Mockito.verify(userService, Mockito.times(1).getUserById(Mockito.eq(3)));

2)验证调用顺序,验证 userService 是否先调用 getUserById 两次,并且第一次的参数是 3,第二次的参数是 5,然后才调用 insertUser 方法。

InOrder inOrder = Mockito.inOrder(userService);
inOrder.verify(userService).getUserByid(3);
inOrder.verify(userService).getUserById(5);
inOrder.verify(userService).insertUser(Mockito.any(User.class));

4.Spring中mock任何容器内对象

4.1 Spring中正常使用Mockito

正常使用 spring 和mockito 中,我们把需要的 mock 的 ApiSerivce 给 mock 掉是通过如下代码实现:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })
public class SpringMockitoTest {

    @Mock
    private ApiService mockApiService;

    @Before
    public void initMocks() {
        MockitoAnnotations.initMocks(this);
        when(mockApiService.test()).thenReturn("ok");
    }

    @Test
    public void should_success_when_testApiService() {
        String result = mockApiService.test();
        Assert.assertEquals("ok", result);
    }
}

@Component
public class ApiService {

    @Autowired
    private TestApiService testApiService;

    public String test() {
        String connect = testApiService.connect();
        connect += "test";//test自己的业务
        return connect;
    }
}

@Component
public class TestApiService {
    public String connect() {
        return "error";
    }

    public String  findFromDb() {
        return "db_data";
    }
}

4.2 Spring中mock任何容器内的对象

当我们想把 TestApiService 中的 connect 方法 mock 掉,这样可以测试我们自己的代码,也就是 ApiService 中 test 方法自己的业务。

因为 TestApiService 是 Spring 容器管理的 Bean,并且 ApiService 中使用到 TestApiService,所以我们把 ApiService 中引用的 TestApiService 替换成我们的 mock 对象即可。

Spring 框架中有个反射工具 ReflectionTestUtils,可以把一个对象中属性设置为新值。用法如下:

ReflectionTestUtils.setField(apiService, "testApiService", spyTestApiService);

把我们 mock 的 testApiService 放到 apiService 中,这样 apiService 调用的就是我们 mock 的对象了;但是默认 Spring 中 apiService 对象是代理对象,不能直接把值设置到属性上,所以我们自己写个小的工具类,在最后如下:

ReflectionTestUtils.setField(AopTargetUtils.getTarget(apiService), "testApiService", spyTestApiService);

完整demo:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:testApplicationContext.xml" })
public class SpringMockitoTest {

    @Autowired
    private ApiService apiService;
    @Mock
    private TestApiService spyTestApiService;
    @Autowired
    private TestApiService testApiService;

    @Before
    public void initMocks() throws Exception {
        MockitoAnnotations.initMocks(this);
        ReflectionTestUtils.setField(AopTargetUtils.getTarget(apiService), "testApiService", spyTestApiService);
        when(spyTestApiService.connect()).thenReturn("ok");
    }

    @After
    public void clearMocks() throws Exception {
        ReflectionTestUtils.setField(AopTargetUtils.getTarget(apiService), "testApiService", testApiService);
    }

    @Test
    public void should_success_when_testApiService() {
        String result = apiService.test();
        Assert.assertEquals("oktest", result);
    }
}

@Component
public class ApiService {

    @Autowired
    private TestApiService testApiService;

    public String test() {
        String connect = testApiService.connect();
        connect += "test";//test自己的业务
        return connect;
    }
}

@Component
public class TestApiService {
    public String connect() {
        return "error";
    }

    public String  findFromDb() {
        return "db_data";
    }
}

public class AopTargetUtils {
    /**
     * 获取 目标对象
     * @param proxy 代理对象
     * @return
     * @throws Exception
     */
    public static Object getTarget(Object proxy) throws Exception {
        if(!AopUtils.isAopProxy(proxy)) {
            return proxy;//不是代理对象
        }
        if(AopUtils.isJdkDynamicProxy(proxy)) {
            return getJdkDynamicProxyTargetObject(proxy);
        } else { //cglib
            return getCglibProxyTargetObject(proxy);
        }
    }

    private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
        Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
        h.setAccessible(true);
        Object dynamicAdvisedInterceptor = h.get(proxy);
        Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
        advised.setAccessible(true);
        Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
        return target;
    }


    private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
        Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
        h.setAccessible(true);
        AopProxy aopProxy = (AopProxy) h.get(proxy);
        Field advised = aopProxy.getClass().getDeclaredField("advised");
        advised.setAccessible(true);
        Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
        return target;
    }
}

最后就是注意测试之后把spring对象还原,尤其是在跑 maven test 的时候,否则可能会影响其他人的测试。

5.Mockito的局限性

  • 不能 Mock 静态方法;
  • 不能 Mock private 方法;
  • 不能 Mock final class。

因此在写代码时,需要做良好的功能拆分,才能够使用 Mockito 的 Mock 技术,帮助我们降低测试时 Bean 的耦合度。

6.总结

Mockito 是一个非常强大的框架,可以在执行单元测试时帮助我们模拟一个 Bean,提高单元测试的稳定性。

并且大家可以尝试在写代码时,从 Mock 测试的角度来写,更能够写出功能切分良好的代码架构,像是如果有把专门和外部服务沟通的代码抽出来成一个 Bean,在进行单元测试时,只要通过 Mockito 更换掉那个 Bean 就行了。





参考地址:

1.Mockito 简介,https://www.cnblogs.com/satire/p/14846492.html

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

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

相关文章

计算机组成原理-动态链接库-笔记

Linux 下的 ELF 文件格式 Windows 的可执行文件格式是一种叫作PE&#xff08;Portable Executable Format&#xff09;的文件格式 动态链接库 这些机器码必须是“地址无关”的。也就是说&#xff0c;我们编译出来的共享库文件的指令代码&#xff0c;是地址无关码&#xff08;…

10个可以实现高效工作与在线赚钱的 AI 工具网站

自 2020 年以来&#xff0c;内容开发领域已经感受到人工智能工具的存在。 目前&#xff0c;营销人员和内容创作者正在利用这些工具来加快他们的工作流程。 如果您拥有最流行的 AI 工具之一&#xff0c;例如 CopyAI、Jasper AI 或 Content at Scale&#xff0c;您可能正在考虑…

申论套卷 | 要点杂、乱、碎的材料如何快速分类整理?

试卷来源&#xff1a;2020年全国联考上半年材料1A省C市Y区文化馆&#xff08;非物质文化遗产保护中心&#xff09;作为政府设立的公益性公共文化事业单位&#xff0c;始终坚持公益文化发展方向&#xff0c;面向基层、贴近百姓、服务大众&#xff0c;积极组织各种大型活动&#…

Ubuntu中安装matelab2020a

Ubuntu中安装matelab2020a1 matelab下载2 安装步骤3 激活matelab4 创建快捷方式我的Ubuntu版本是20.041 matelab下载 matelab官网https://www.mathworks.com/ 点击右上角的get matelab&#xff0c;进入下载页面 没有账号的同学可以先去注册一个&#xff0c;推荐使用教育邮箱&…

错误: tensorflow.python.framework.errors_impl.OutOfRangeError的解决方案

近日&#xff0c;在使用CascadeRCNN完成目标检测任务时&#xff0c;我在使用这个模型训练自己的数据集时出现了如下错误&#xff1a; tensorflow.python.framework.errors_impl.OutOfRangeError: PaddingFIFOQueue _1_get_batch/batch/padding_fifo_queue is closed and has in…

前端JS内存管理

JS内存管理 内存原理&#xff1a; 任何变成语言在执行的时候都需要操作系统来分配内存&#xff0c;只是有些语言需要手动管理分配的内存有些语言有专门来管理内存的方式 如 JVM 了解以上的概念之后&#xff0c;我们再来了解一下大致的内存周期 分配需要的内存使用内存在不使用…

Linux- 系统随你玩之--好用到炸裂的系统级监控、诊断工具

文章目录1、前言2、lsof介绍2.1、问题来了&#xff1a; 所有用户都可以采用该命令吗&#xff1f;3、 服务器安装lsof3.1、安装3.2、检查安装是否正常。4、lsof 命令4.1、常用功能选项4.2、输出内容4.2.1 、FD和 TYPE列5、 lsof 命令实操常见用法6 、常用组合命令7、 结语1、前言…

OpenHarmony 3.2 Beta Audio——音频渲染

一、简介Audio是多媒体子系统中的一个重要模块&#xff0c;其涉及的内容比较多&#xff0c;有音频的渲染、音频的采集、音频的策略管理等。本文主要针对音频渲染功能进行详细地分析&#xff0c;并通过源码中提供的例子&#xff0c;对音频渲染进行流程的梳理。二、目录foundatio…

无线WiFi安全渗透与攻防(一)之无线安全环境搭建

无线安全环境搭建 1.802.11标准 &#xff08;1&#xff09;.概念 802.11标准是1997年IEEE最初制定的一个WLAN标准&#xff0c;工作在2.4GHz开放频段&#xff0c;支持1Mbit/s和2Mbit/s的数据传输速率&#xff0c;定义了物理层和MAC层规范&#xff0c;允许无线局域网及无线设备…

Crack:LightningChart .NE​​T 10.4.1中的新功能

数据游标 Lightningchart .NET UWP 展示应用 在以前的版本中&#xff0c;LightningChart .NET 提供了不同的工具来实现数据跟踪功能&#xff0c;但都需要额外的用户编码。 现在可以使用 DataCursor 浏览 ViewXY 系列。系列数据值由这个新类/对象显示在鼠标位置或鼠标位置附近。…

【matplotlib】可视化解决方案——如何解决matplotlib中文乱码问题

问题概述 Matplotlib 默认不支持中文字体&#xff0c;这是因为 matplotlib 只支持 ASCII 字符&#xff0c;但是国人使用 matplotlib 肯定需要中文标注。如下图所示&#xff0c;当不对 Matplotlib 进行设置&#xff0c;而直接使用中文时&#xff0c;绘制的图像会出现中文乱码。…

为什么我选择收费的AdsPower指纹浏览器?

在决定开始用指纹浏览器之前&#xff0c;东哥我们团队找了市面上很多产品去测试。最后&#xff0c;还是决定用AdsPower。每个人的使用感受都不一样&#xff0c;我就说几个东哥和我们团队用得顺手的点&#xff0c;大家在选择指纹浏览器的时候也可以做一个参考。 一、指纹环境强大…

3月5日,加入线上对话,点燃科技行业女性影响力!

对话升级&#xff0c;点燃科技行业女性影响力&#xff01; &#x1f44b; 2022 年&#xff0c;Jina AI 联合 14 家合作伙伴&#xff0c;首次举办了「Impact Tech, She Can」线上对话&#xff0c;11 位嘉宾与 200 多位参会者分享了如何在科技行业内打造自身影响力。 &#x1f38…

html基础(h、p、br、hr、文本加粗倾斜下划线删除线、资源路径、音频、视频、超链接)

1标题<h><h1>1级标题</h1><h2>2级标题</h2><h3>3级标题</h3><h4>4级标题</h4><h5>5级标题</h5><h6>6级标题</h6>2段落<p>和换行<br><p>段落标签</p><p> fgghikg…

大数据技术——面向对象编程基础

类类的定义字段定义:用val或var关键字进行定义方法定义:使用new关键字创建一个类的实例类成员可见性Scala类中所有成员的默认可见性为公有&#xff0c;任何作用域内都能直接访问公有成员除了默认的公有可见性&#xff0c;Scala也提供private和protected其中&#xff0c;private…

真涨脸,我用 Python 为朋友自动化整理表格

今天&#xff0c;在工作的时候&#xff0c;我的美女同事问我有没有办法自动生成一个这样的表格&#xff1a; 第一列是院校科目&#xff0c;第二列是年份&#xff0c;第三列是数量。 这张表格是基于这一文件夹填充的&#xff0c;之前要一个文件夹一个文件夹打开然后手动填写年份…

测牛学堂:软件测试python之unittest框架总结(3)

python之unittest添加整个测试类执行 如果我们的测试用例比较多的话&#xff0c;测试套件对象通过addTest一个一个添加比较麻烦&#xff0c;可以通过添加一个类的方法&#xff0c; 去执行这个类里面的所有测试方法 套件对象.addTest(unittest.makeSuite(测试类名)) import un…

“终于我从字节离职了...“一个年薪50W的测试工程师的自白...

我递上了我的辞职信&#xff0c;不是因为公司给的不多&#xff0c;也不是因为公司待我不好&#xff0c;但是我觉得&#xff0c;我每天看中我憔悴的面容&#xff0c;每天晚上拖着疲惫的身体躺在床上&#xff0c;我都不知道人生的意义&#xff0c;是赚钱吗&#xff1f;是为了更好…

3.2滑动窗口

滑动窗口*** 题目链接 视频讲解 属于单调队列的模板题 如果每次移动窗口&#xff0c;然后在窗口中循环遍历查找最大值&#xff0c;时间复杂度太高 解决思路&#xff1a; 维护一个单调队列&#xff0c;其中head永远指的是当前窗口中最大的值&#xff0c;从head到tail元素递减。…

【Unity VR开发】结合VRTK4.0:创建圆盘

语录&#xff1a; 茶若相似&#xff0c;味不必如一。但凡茗茶&#xff0c;一泡苦涩&#xff0c;二泡甘香&#xff0c;三泡浓沉&#xff0c;四泡清洌&#xff0c;五泡清淡&#xff0c;此后&#xff0c;再好的茶也索然无味。诚似人生五种&#xff0c;年少青涩&#xff0c;青春芳…