目录

  • 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

Mockito 入门相关推荐

  1. mockito入门_Mockito入门

    mockito入门 本文是我们名为" 用Mockito测试 "的学院课程的一部分. 在本课程中,您将深入了解Mockito的魔力. 您将了解有关"模拟",&qu ...

  2. SpringBoot - 单元测试利器Mockito入门

    文章目录 Mock 测试 What's Mockito 使用 Mockito pom依赖 Demo Code [常规操作] [Mockito] thenReturn thenThrow verify ...

  3. 单元测试(三) mockito入门

    目录 一.什么是mock测试 二.什么是Mockito 三.快速开始 quickstart 四.3种不同的mock方式 五. Stubbing 六.spying 七.Mockito Argument ...

  4. Mock框架Mockito入门教程

    在开发中,我们经常会依赖同事或者第三方提供的接口,如果该接口无法正常工作:比如接口正在修复,或者网络异常等.那么便会对需要依赖该接口的开发造成很大影响. 遇到这种情况,我们可能会想到模拟该接口以提供正 ...

  5. Mockito 简明教程

    原文同步至 http://waylau.com/mockito-quick-start/ Mock 测试是单元测试的重要方法之一.本文介绍了基于 Java 语言的 Mock 测试框架 -- Mocki ...

  6. 5分钟了解Mockito

    原文链接:http://liuzhijun.iteye.com/blog/1512780 一.什么是mock测试,什么是mock对象? 先来看看下面这个示例: 从上图可以看出如果我们要对A进行测试,那 ...

  7. spring中使用mockito

    1 mockito介绍和入门 官方:https://github.com/mockito/mockito 入门: 5分钟了解Mockito http://liuzhijun.iteye.com/blo ...

  8. Mockito的简单使用

    Mock 测试的作用 Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)或者不容易获取比较复杂 的对象(如 JD ...

  9. 【转】Mockito教程

    原文地址:https://www.cnblogs.com/Ming8006/p/6297333.html 1 Mockito 介绍 [3] 1.1 Mockito是什么? Mockito是mockin ...

最新文章

  1. 小程序真机调试访问不了接口_小程序入门
  2. /proc/diskstats各字段解析
  3. linux服务器报Too many open files的解决方法
  4. 大剑无锋之如何查看一个java进程的堆内存使用情况(jps,jstack,jmap)【面试推荐】
  5. linux目录树(书本上看到)
  6. 动手学深度学习(PyTorch实现)(一)--线性回归
  7. java 求向量的均值,标准数组——向量
  8. python pytest mark
  9. Excel将多个工作簿加载到SQL Server中
  10. coin collector(一道测试题)
  11. 汇编 第二章 寄存器
  12. java pdf tiff_java-使用iText将TIFF转换为PDF的多线程方法
  13. 网页开发者模式调整到手机模式_突破极限?ROG 游戏手机 3 内藏 160Hz 刷新率模式...
  14. matable的作用
  15. Camshift原理
  16. 游戏后台管理工具帮助文档
  17. 计算机具有逻辑思维,逻辑思维能力
  18. 快捷键,photoshop常用快捷键大全
  19. 一个女测试工程师的成长之路
  20. 数学连乘和累加运算符号_3,7,5之间加数学运算符号使结果等于8?

热门文章

  1. linux命令中service ntpd restart,Linux 时间同步 ntpd
  2. linux 调试ntp服务,时钟同步Linux NTPD设置、调试
  3. QQ小程序开发与发布小教程
  4. Qt学习之路之 QTextEdit 重温
  5. AtCoder Beginner Contest 254
  6. 微信小程序交互性能优化利器WXS的使用(一)
  7. 用技术换点零花钱:分享几个接外包私活的网站
  8. win10的版本升级——NUC7i7安装有感
  9. Linux上安装部署Solr-4.10.4并测试
  10. “21天好习惯”第一期——16