使用 Mockito 的 @InjectMocks 创建被测试类实例

初识 Mockito 这个测试框架后,我们要使用 Mock 的属性创建一个被测试类实例时,大概会下面这么纯手工来打造。

假定类 UserService 有一个属性 UserDao userDao, 需要构造 UserService 实例时 Mock 内部状态

UserDao userDao = Mockito.mock(UserDao.class);
UserService testMe = new UserService(userDao);

如此,userDao 的行为就可以自由模拟了,这种纯手工方式都不需要给测试类添加

@RunWith(MockitoJunitRuner.class)
//或
MockitoAnnotations.initMocks(this);

因为上面两句是给 Mockito 的注解使用的。

如果所有的 Mock 对象全部通过手工来创建,那就不容易体现出 Mockito 的优越性出来。因此对于被测试对象的创建,Mock 属性的注入应该让 @Mock 和 @InjectMocks 这两个注解大显身手了。

标注在实例变量上的 @Mock 相当于是 Mockito.mock(Class) 创建了一个 Mock 对象,而 @InjectMock 标的实例会寻找到相应 Mock 属性想法构造出被测试类的实例。看下面的例子:

UserService 类

public class UserService {private UserDao userDao;public UserService(UserDao userDao) {System.out.println("Constructor called");this.userDao = userDao;}public UserDao getUserDao() {return userDao;}}

UserServiceTest 类

@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserDao userDao;@InjectMocksprivate UserService testMe;@Testpublic void testInjectMocks() {System.out.println(testMe.getUserDao().getClass());}}

Constructor called上面测试用例的输出为

class cc.unmi.UserDao$MockitoMock$878185941

证明了 Mock 对象 userDao 成功的通过构造函数注入了 testMe 实例。

除了通过构造函数注入 Mock 的属性外, @InjectMocks  还能通过 setter 方法,属性注入。私有的构造函数,setter 方法,属性都无法阻止 @InjectMocks 注入 Mock 对象。

下面是理解自 Mockito 官方对 @InjectMocks 的 JavaDoc 说明,链接:InjectMocks - mockito-core 2.13.0 javadoc

  1. Mockito 尝试按 非默认构造函数setter 方法属性 的顺序来注入 Mock 对象。如果存在一个有参数的构造函数,那么 setter 方法 和 属性  注入都不会发生。也就是说 非默认构造函数 不会与后两种方式同时发生,但找不到 setter 注入的 Mock 对象还会尝试用 属性 来直接注入。
  2. 如果 @InjectMocks 对象只有默认构造数,那么会调用该默认构造函数,并且依次采用下面两种方式注入属性。
  3. 非默认构造函数注入: Mockito 会选择参数个数最多的构造函数(称之为最大构造函数) -- 这样可以尽可能注入多的属性。但是有多个最大构造函数,Mockito 究竟选择哪一个就混乱,测试时应该避免这种情况的发生。
  4. 如果构造函数中含有不可 Mock 的参数(基本类型), 则该构造函数将被 @InjectMocks 忽略掉。
  5. setter 方法注入: 和 Spring 类似,Mockito 首先根据属性类型(或擦除类型)找到 Mock 对象。存在多个相同类型 Mock 对象则按名称(@Mock(name="userDao1"))进行匹配,默认名称为空。不能按名称匹配到的话,可能会选择最后声明的那个,不确定性。
  6. 属性 注入: 按 Mock 对象的类型或是名称的匹配规则与 setter 方法注入 是一样的。

现在来开始有事实验证上面理解的 @InjectMocks 理论:

调用最大构造函数,调用了非默认构造函数将不会采用 setter 方法 和 属性 注入

public class UserService {public UserDao userDao;private UserService(String s1) {System.out.println("Constructor 1 called");}private UserService(String s1, String s2) {System.out.println("Constructor 2 called");}public void setUserDao(UserDao userDao) {System.out.println("call setter");this.userDao = userDao;}}@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserDao userDao;@InjectMocksprivate UserService testMe;@Testpublic void testInjectMocks() {System.out.println(testMe.userDao);}}

上面测试执行输出为:

Constructor 2 called
null

同时证明了私有的构造函数一样被调用。

@InjectMocks 调用了默认构造函数后还能同时应用 setter 方法 和 属性 注入两种式

public class UserService {public UserDao userDao;private BookDao bookDao;public UserService() {System.out.println("Constructor 0 called");}private void setUserDao(UserDao userDao) {System.out.println("call setter");this.userDao = userDao;}public BookDao getBookDao() {return this.bookDao;}}@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserDao userDao;@Mockprivate BookDao bookDao;@InjectMocksprivate UserService testMe;@Testpublic void testInjectMocks() {System.out.println(testMe.userDao.getClass());System.out.println(testMe.getBookDao().getClass());}}

测试代码输出如下:

Constructor 0 called
class cc.unmi.UserDao$MockitoMock$1978393893
class cc.unmi.BookDao$MockitoMock$910006861

默认构造函数调用了,userDao 通过  setter 方法注入的,bookDao 通过属性直接注入的。把 setUserDao(..) 方法和 bookDao  设置为私有也是为了证明可见性不是障碍,当然 public 的更不是事。

含有基本类型参数的构造函数将被 @InjectMocks 忽略掉

public class UserService {public UserDao userDao;public UserService() {System.out.println("Constructor 0 called");}private UserService(UserDao userDao, boolean flag) {System.out.println("Constructor 2 called");}}@RunWith(MockitoJUnitRunner.class)public class UserServiceTest {@Mockprivate UserDao userDao;@InjectMocksprivate UserService testMe;@Testpublic void testInjectMocks() {System.out.println(testMe.userDao.getClass());}}

执行测试用例的输出为:

Constructor 0 called
class cc.unmi.UserDao$MockitoMock$286493746

由于无法构造出 Mock 的 boolean 类型,所以 UserService(UserDao userDao, boolean flag) 被忽略,调用了默认构造函数,并且 userDao 通过属性进行了注入。

多个相同类型的 Mock 对象通过名称进行匹配

public class UserService {public UserDao userDao2;private UserService(UserDao userDao1, String abc) {System.out.println("Constructor 2 called");this.userDao2 = userDao1;}
}@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {@Mock(name = "userDao1")private UserDao userDao1;@Mock(name = "userDao2")private UserDao userDao2;@InjectMocksprivate UserService testMe;@Testpublic void testInjectMocks() {Assert.assertEquals(userDao1, testMe.userDao2);}}

输出为:

Constructor 2 called

UserService 类中对 userDao2 和 userDao1 名称进行错位安排是为了证明名称匹配是根据注入点处的名称对比的。例如

  1. 构造函数注入,根据参数名进行匹配
  2. setter 方法注入,根据 setter 方法名, 如 setUserDao1(..), 或 setUserDao2(..) 匹配的,与方法参数无关
  3. 属性注入自然是以属性名本身为准

同时该例也证明了构造函数 UserService(UserDao userDao1, String abc) 对 @InjectMocks 是可见的,因为 String 是非基本类型,也是可以 Mock String 类型的。

因此,需要我们留意的是,产品代码构造函数的变动可能会改变测试代码的行为,或是导致测试的失败。

@InjectMocks 只能注入 Mock 对象,例如以下均是 Mock 对象

  1. UserDao userDao = Mockito.mock(UserDao.class);
  2. @Mock private UserDao userDao;
  3. @Mock private UserDao userDao = new UserDao();    //Mockito 将会对 userDao 重新赋值为一个  Mock 对象
  4. UserDao userDao = spy(new UserDao());

如果是一个普通对象,例如下面的声明

private UserDao userDao = new UserDao();@InjectMocksprivate UserService testMe;

@InjectMocks 如何费尽心思都无法把这个  userDao  注入到 testMe  测试对象中去的。对它 spy 一下就可以被注入了。

@Mock 和 @InjectMocks 会把自己赋的值丢弃

前面提到 @Mock private UserDao userDao = new UserDao(); 最终的 userDao 是一个 Mock  对象,@InjectMocks  也一样

@InjectMocksprivate UserService testMe = new UserService(); 

虽然会调用一下 new UserService() 创建一个对象,但最终的值是由 @InjectMocks 产生的。


备注一个使用 @Mock 对象创建被测试实例的错误

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {@Mockprivate UserDao userDao;private UserService testMe = new UserService(userDao); //此时 userDao 还是 null@Beforepublic void setup() {testMe = new UserService(userDao); //这里的 userDao 才是一个 Mock 对象}}

静态测试类的示例

@RunWith(PowerMockRunner.class)
@PrepareForTest({SpringContext.class,KeywordRuleCacheData.class
})
public class KeywordRuleCacheDataRefreshDealTest {@InjectMocksKeywordRuleCacheDataRefreshDeal keywordRuleCacheDataRefreshDeal;@Testpublic void run() throws BaseAppException {PowerMockito.mockStatic(SpringContext.class);PowerMockito.mockStatic(KeywordRuleCacheData.class);PowerMockito.when(KeywordRuleCacheData.refushKeywordRuleCacheData()).thenReturn(true);keywordRuleCacheDataRefreshDeal.run();}}

Java测试工具Mock详解相关推荐

  1. Java基准测试工具JMH详解

    Java基准测试工具JMH详解 1.JMH概述 1.1 JMH简介 1.2 JMH与JMeter区别 1.3 JMH注解说明 2.JMH验证 2.1 创建项目 2.2 引入依赖 2.3 启动异常解决 ...

  2. Android测试工具-Monkey详解

    Monkey详解 Monkey详解 一 Monkey简介 二 Monkey测试环境配置 三 执行Monkey 四 常用monkey命令 五 Monkey测试问题分析 Monkey详解 一 Monkey ...

  3. Web性能压力测试工具——Siege详解

    Siege是一款开源的压力测试工具,设计用于评估WEB应用在压力下的承受能力.可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行. ...

  4. java 测试磁盘io,详解三种Linux测试磁盘IO性能的方法总结,值得收藏

    概述 在磁盘测试中我们一般最关心的几个指标分别为:iops(每秒执行的IO次数).bw(带宽,每秒的吞吐量).lat(每次IO操作的延迟). 当每次IO操作的block较小时,如512bytes/4k ...

  5. Java诊断工具Arthas详解

    Arthas Arthas 是 Alibaba 开源的 Java 诊断工具 作用 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 我改的代码为什么没有执行到?难道是我没 ...

  6. (转)MySQL自带的性能压力测试工具mysqlslap详解

    mysqlslap 是 Mysql 自带的压力测试工具,可以模拟出大量客户端同时操作数据库的情况,通过结果信息来了解数据库的性能状况 mysqlslap 的一个主要工作场景就是对数据库服务器做基准测试 ...

  7. ab 服务器压力测试工具 使用详解

    ab的全称是Apache Bench,是Apache自带的网络压力测试工具,相比于LR.JMeter,是我所知道的 Http 压力测试工具中最简单.最通用的. ab命令对发出负载的计算机要求很低,不会 ...

  8. Linux下磁盘IO读写测试工具-FIO详解

    FIO简介 FIO是Linux下开源的一款IOPS测试工具,主要用来对磁盘进行压力测试和性能验证. 它可以产生许多线程或进程来执行用户特定类型的I/O操作,通过编写作业文件(类似于k8s的yaml)或 ...

  9. android 稳定性测试工具,APP 稳定性测试工具-Fastbot_Android详解

    基于monkey的二次开发,约束monkey的行为,比monkey更智能. 写在开始 monkey测试的随机性概率过大,导致其效率并不能达到预期.有时可能遍历了很久,依旧与最有可能发生问题的部分擦肩而 ...

  10. mysql 带宽测试工具_MySQL自带的性能压力测试工具mysqlslap详解

    使用语法如下: # MySQLslap [options] 常用参数 [options] 详细说明: --auto-generate-sql, -a 自动生成测试表和数据,表示用mysqlslap工具 ...

最新文章

  1. 外部的Navicat连接docker中的mysql
  2. Zend Studio 12 windows 无限期试用
  3. Android 实现布局动态加载
  4. 六类网线和超六类网线有什么区别呢 怎么区分
  5. 事务处理与事务的隔离级别
  6. 【LeetCode】【HOT】437. 路径总和 III(DFS)
  7. 【guava】大数据量下的集合过滤—Bloom Filter
  8. 使用python标准库urllib2访问网页
  9. Hadoop热添加删除节点(含Hbase)
  10. 预印本(Preprint)及出版商的投稿政策
  11. awr报告与statspack报告
  12. Atitit.java 反编译 工具  attilax 总结
  13. 计算机毕业设计ssm飞机售票管理系统63z52系统+程序+源码+lw+远程部署
  14. 双循环背景下的全球供应链机遇与挑战
  15. C#输入分数自动成绩评级
  16. Object Detection(目标检测神文)(二)
  17. ERP2021青岛理工信管期末考试重点
  18. IT技术外包公司值得去吗? | 关于 ICC Contractor 你应该知道的!
  19. Linux浏览器无法访问网络解决方案
  20. 如何培养自己的幽默感

热门文章

  1. Java中VO/DTO/DO/PO/POJO/BO/DAO概念及其区别
  2. ubuntu 18.04.1安装hadoop3.1.2
  3. hdu 5101 n集合选2个不同集合数使和大于k
  4. Linux操作系统配置基础详解:GRUB入门 (转)
  5. 在 Windows 下远程桌面连接 Linux - VNC 篇
  6. [翻译]Popfly系列课程7 –深入幕后:使用 Popfly学习XML的初学者指南
  7. FPGA 二选一数据选择器
  8. 华众 mysql_华众6.5虚拟主机管理系统SQL注入漏洞利用
  9. 的唯一性_原神:被氪金玩家淹没的角色,输出很高,技能具有唯一性
  10. 理解python 文件首行(Shebang)