Mockito 入门

article/2025/9/15 12:05:03

在这里插入图片描述

目录

    • 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 {@Autowiredprivate 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@Autowiredprivate UserService userService;@Testpublic 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 {@Autowiredprivate UserService userService;@MockBeanprivate UserDao userDao;@Testpublic 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 {@Mockprivate ApiService mockApiService;@Beforepublic void initMocks() {MockitoAnnotations.initMocks(this);when(mockApiService.test()).thenReturn("ok");}@Testpublic void should_success_when_testApiService() {String result = mockApiService.test();Assert.assertEquals("ok", result);}
}@Component
public class ApiService {@Autowiredprivate 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 {@Autowiredprivate ApiService apiService;@Mockprivate TestApiService spyTestApiService;@Autowiredprivate TestApiService testApiService;@Beforepublic void initMocks() throws Exception {MockitoAnnotations.initMocks(this);ReflectionTestUtils.setField(AopTargetUtils.getTarget(apiService), "testApiService", spyTestApiService);when(spyTestApiService.connect()).thenReturn("ok");}@Afterpublic void clearMocks() throws Exception {ReflectionTestUtils.setField(AopTargetUtils.getTarget(apiService), "testApiService", testApiService);}@Testpublic void should_success_when_testApiService() {String result = apiService.test();Assert.assertEquals("oktest", result);}
}@Component
public class ApiService {@Autowiredprivate 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 { //cglibreturn 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://chatgpt.dhexx.cn/article/HjhTjryP.shtml

相关文章

Mock工具之Moco使用教程

目录 一、什么是Moco二、安装&配置三、接口配置&测试3.1 第一个简单的请求&#xff1a;3.2 Get请求&#xff1a;3.3 Post请求&#xff1a;3.4 返回值为Json格式的请求&#xff1a;3.5 带cookie信息才能访问的post请求&#xff1a;3.6 重定向请求&#xff1a;3.7 前后端…

mock功能

目标 了解mock的作用及使用场景&#xff1b; mock使用场景 mock&#xff1a;假的 前端程序员提到的mock数据的含义是&#xff1a;真的假数据 真的&#xff1a;符合接口规范要求的。 假数据&#xff1a;数据是人为创建出来的&#xff0c;不是真正的业务数据。 什么时候需…

mock的介绍

1.什么是mock mock测试是以可控的方式模拟真实的对象行为。程序员通常创造模拟对象来测试对象本身该具备的行为&#xff0c;很类似汽车设计者使用碰撞测试假人来模拟车辆碰撞中人的动态行为 2.为什么要使用mock 之所以使用mock测试&#xff0c;是因为真实场景很难实现或者短…

Mock介绍

mock的定义&#xff08;what&#xff09;&#xff1a; mock是在测试过程中&#xff0c;对于一些不容易构造/获取的对象&#xff0c;创建一个mock对象来模拟对象的行为 为什么要使用mock&#xff08;why&#xff09;&#xff1a; 在做单元测试过程中&#xff0c;经常会有以下的…

Mockito单元测试

文章目录 Mockito单元测试为什么要使用Mock?导入依赖import导入包使用Mock模拟测试某个类中的某个方法是否可以成功执行使用Mock模拟某个类的方法&#xff0c;自己给这个方法返回我们指定的值使用Mock模拟某个方法调用后会抛出指定的异常使用Mock模拟测试某个类中的某个方法(加…

javascript 中怎么判断为数字类型

javascript中判断变量是否为数字类型抄的方法有两种&#xff1a; 方法一&#xff1a; function isnum(s) { if(s!null){ var r,re; re /\d*/i; //\d表示数字,*表示匹配多个数字 r s.match(re); return (rs)?true:false; } return false; } 方法二&#xff1a; function isnu…

js判断字符串是不是一个纯数字

1.使用隐式转换判断 使用parseInt()/parseFloat()会把其他类型转换为数值&#xff0c;转换结果为数值或NaN,转换字符串的时候&#xff0c;头部内容有数值就会截取前面&#xff0c;如123456aff789bb转成123456&#xff0c;后面即使有数字也不会转换处理&#xff0c;开头如果是字…

javaScript 判断是否为数值类型的三种方法

第一种方法:isNaN(value) isNaN : isNaN() 函数用于检查其参数是否是非数字值。 返回true则判断为非数值。缺点&#xff1a;空字符串&#xff0c;空格 &#xff0c;null会做为数字0进行处理&#xff0c;结果为falsehttps://www.w3school.com.cn/js/jsref_isnan.anusp 第二种方法…

JS中判断变量是否为数字方法

推荐教程&#xff1a;《JavaScript视频教程》 JavaScript 是一种动态类型语言&#xff0c;这意味着解释器在运行时确定变量的类型。实际上&#xff0c;这也允许我们在相同的代码中使用相同的变量来存储不同类型的数据。如果没有文档和一致性&#xff0c;我们在使用代码时并不总…

matlab绘制图形中,常用函数调用(num2str,disp,gcf,hold on,plot,axis,subplot,line,stairs,grid,set,gca)

num2str 将数字转换为字符数组 s = num2str(A) 将数字数组转换为表示数字的字符数组。输出格式取决于原始值的大小。 num2str对于使用数值标注和标题绘图非常有用。 s = num2str(A,precision) 返回一个字符数组,表示具有精度指定的最大有效位数的数字。 s = num2str(A,fo…

Matlab中num2str函数使用

目录 语法 说明 示例 浮点值的默认转换 指定精度 指定格式 num2str函数将数字转换为字符数组。 语法 s num2str(A)s num2str(A,precision)s num2str(A,formatSpec) 说明 s num2str(A) 将数值数组转换为表示数字的字符数组。输出格式取决于原始值的量级。num2str 对…

C/C++ split函数 num2str str2num函数

C/C split函数,num2str及str2num函数实现 在C98及其以下版本的C/C中&#xff0c;没有现成的字符串split函数和num2str及str2num函数&#xff0c;对于竞赛不能使用C11的我来说非常头疼&#xff0c;通过学习stringstream和strtok实现了自己的split函数&#xff0c;通过stringstr…

matlab 中num2str函数的使用

运行ECO代码报错&#xff1a;转义字符 ‘\d’ 无效。有关支持的特殊字符&#xff0c;请参阅 ‘doc sprintf’。 num2str函数功能&#xff1a;将数字转换为字符串 具体所有功能可见官网。 这里使用的格式为&#xff1a; str num2str(A, format) 将 format 指定的格式应用到 A …

js基础运用——js用*打印三角形,菱形

js基础运用 新手见解&#xff0c;有不足或更简单的方法希望各位指点 1.用*排列直角三形 &#xff08;图一&#xff09; &#xff08;图二&#xff09; 图一图二是js语句利用*号画出来的直角三角形&#xff0c;方法思路都一致&#xff0c;下面以图一为例讲一下本人所用的方法…

java:打印十字图

java:打印十字图 题目 问题描述 小明为某机构设计了一个十字型的徽标&#xff08;并非红十字会啊&#xff09;&#xff0c;如下所示&#xff1a;..$$$$$$$$$$$$$.. ..$...........$.. $$$.$$$$$$$$$.$$$ $...$.......$...$ $.$$$.$$$$$.$$$.$ $.$...$...$...$.$ $.$.$$$.$.$$…

JAVA 利用JFram,JPanel,JScrollPane简单文本框实现

****1.JFram 支持通用窗口的所有基本功能 2…JPanel是一个无边框&#xff0c;不可以被移动的&#xff0c;放大的&#xff0c;缩小的&#xff0c;或者关闭的面板&#xff0c;默认布局是FlowLayout&#xff08;也可用setLayout&#xff08;&#xff09;来设定&#xff09;** 3……

deflater java_Java Deflater finished()用法及代码示例

如果已到达压缩数据输出流的末尾&#xff0c;则java.util.zip中Deflater类的finished()函数将返回true。 函数签名&#xff1a; public boolean finished() 用法: d.finished(); 参数&#xff1a;该函数不需要参数 返回类型&#xff1a;该函数返回布尔值&#xff0c;即如果所有…

jQuery API ( 三 ) -------- 链式编程 与 修改样式方法

今天这篇文章是 jQuery API 的第三篇&#xff0c;今天心情很好&#xff0c;因为破了300粉&#xff0c;希望所有代码人前端人在编程与写作的路上都能一帆风顺&#xff0c;全都早日拿认证。okk言归正传&#xff0c;这篇文章将带大家走进链式编程的世界&#xff0c;这也是 jQuery …

jsfiddle 使用教程

最近有许多的Css 3 demo&#xff0c;因此为了方便查阅&#xff0c;就将demo部分放在 jsfiddle &#xff0c;方便日后翻阅。 这是 JSFIDDLE 的官网文档&#xff0c;都是英文&#xff0c;不过对照看还是可以的&#xff1a;官方文档 HTML区域&#xff1a; 它的HTML区域已经包含 ht…

Jfinal 框架 在Html页面使用 #if(调用java方法) #end

欢迎转发分享&#xff0c; 转发请附上本文地址&#xff1a; https://blog.csdn.net/Luoxianxun/article/details/106399780 一、实现需求 实现在html 中使用 #if(调用java类中的方法) #end&#xff1b; 二、使用技术 JFinal后台框架 JFinal 官方文档&#xff1a;https://jfi…