Java Mockito 笔记
Mockito
1 Overview
2 Maven 项目初始化
3 示例
3.1 第一个示例
3.2 自动 Mock
3.3 Mock 返回值
3.4 Mock 参数
3.5 自动注入 Mock 对象
3.6 验证调用次数
3.7 预设 Exception
3.8 Void Mock
3.9 级联 Mock
3.10 部分 Mock
4 FAQ
4.1 注意点
5 References
1 Overview
Mockito 是 Java 中用于 Mock 的一个开源项目。
Mock 用于如下目的
- 如果依赖的外部系统在测试环境下不可用,使用 Mock 跳过外部系统调用并模拟返回结果
- 验证是否的确调用了 Mock 对象
2 Maven 项目初始化
创建 Maven Quick Start 项目
pom.xml 修改如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.lld</groupId>
<artifactId>test.mockito</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>test.mockito</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3 示例
3.1 第一个示例
我们先看如下代码
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import java.util.List; import org.junit.Test; public class FirstTest { @Test
public void verifyBehavior() {
@SuppressWarnings("unchecked")
List<Integer> mock = mock(List.class);
mock.add(1);
mock.clear();
verify(mock).add(1);
verify(mock).clear();
}
}
作为第一个示例,我在此详细解释一下
首先,我们使用 Mockito.mock() 方来来生成 Mock 对象。这里我们需要注意两点:
- 可以直接生成接口的 Mock 对象
- 范型对象 Mock
然后我们调用了 Mock 对象的两个成员方法,在此我们需要更深刻地理解一下 Mock 对象的行为,如果我们在调用 add 方法后打印它的 size,我们会发现结果是 0 而不是 1。也就是说,Mock 对象只是拦截了所有对原始方法的调用并返回对应返回的类型的默认值,而不是真正地实现了这个接口或创建了对象实例。
然后是两个 verify 方法,表示验证 Mock 对象是否调用了对应的方法。注意验证 add 调用时,可以验证输入参数(本例为 1),如果不想验证,只需要确定是否调用,可以使用如下方式验证
import static org.mockito.Matchers.anyInt;
...
verify(mock).add(anyInt());
3.2 自动 Mock
可以使用如下方式自动生成 Mock 对象
import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
...
@Mock
private List<Integer> mock; @Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
3.3 Mock 返回值
下例 mock一个Iterator类,预设当iterator调用next()时第一次返回hello,以后每次都返回world
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import java.util.Iterator;
import org.junit.Test; public class MockReturnTest { @Test
public void verifyBehavior1() {
@SuppressWarnings("unchecked")
Iterator<String> iterator = mock(Iterator.class);
when(iterator.next()).thenReturn("hello").thenReturn("world");
String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
assertEquals("hello world world", result);
}
}
3.4 Mock 参数
可以使用以下 Mockito 对象来模拟任意输入值
import static org.mockito.Matchers.any*
例如 anyString, anyInteger, anyChar 等,也可以使用 any() 方法来生成任意对象,例如
List<String> mock = mock(List.class);
mock.add(any(String.class));
或者使用更简单的 Mockito.any(), 如下所示
import static org.mockito.Mockito.any; List<String> mock = mock(List.class);
mock.add(any());
3.5 自动注入 Mock 对象
对于如下的情况,我们需要 Mock 某对象的内部方法,如下所示,我们需要 Mock MainServer 内部的 OtherService:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; @Component
public class MainService {
@Autowired
OtherService otherService; public void call() {
System.out.println("value is: " + otherService.getValue());
}
}
import org.springframework.stereotype.Component; @Component
public class OtherService {
public String getValue() {
return "real value";
}
}
常规情况下,我们需要手工注入 Mock 对象,如下所示:
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class AutoInjectTest {
@Autowired
MainService service; @Mock
OtherService otherService; @Test
public void manualInjectTest() {
otherService = mock(OtherService.class);
when(otherService.getValue()).thenReturn("mock value");
service.setOtherService(otherService);
service.call();
}
}
PS:需要在 MainService 类中添加 setOtherService() 方法以允许修改 otherService
其中 DemoConfig 是配置类,对于 Spring Boot 框架,Test 类不会自动注入 Autowired 对象,需要使用 Config 类指定加载类,内容如下:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; @Configuration
@ComponentScan({ "com.lld.test" })
public class DemoConfig { }
但更合理的方式是使用 @InjectMocks 注解来自动注入,如下所示
import static org.mockito.Mockito.when; import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class AutoInjectTest {
@Autowired
@InjectMocks
MainService service; @Mock
OtherService otherService; @Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(otherService.getValue()).thenReturn("mock value");
} @Test
public void autoInjectTest() {
service.call();
}
}
注意如下几点:
- 在 @Before 方法中初始化 Mock 对象及自动注入
- 在需要自动注入成员的类上添加 @InjectMocks 注解
另外值得注意的是,@InjectMocks 只会注入当前对象的成员,不会递归深度注入对象,例如,我们如果将 MainService 修改如下:
@Component
public class MainService {
@Autowired
MiddleService middleService; public void callMiddle() {
System.out.println("value is: " + middleService.getValue());
}
}
添加 MiddleService 如下所示
@Component
public class MiddleService {
@Autowired
OtherService otherService; public String getValue() {
return otherService.getValue();
}
}
这样的话,Unit Test 需要修改如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class AutoInjectTest {
@Autowired
MainService service; @Mock
OtherService otherService; @Autowired
@InjectMocks
MiddleService middleService; @Before
public void setup() {
MockitoAnnotations.initMocks(this);
when(otherService.getValue()).thenReturn("mock value");
} @Test
public void autoInjectDeepTest() {
service.callMiddle();
}
}
3.6 验证调用次数
如下代码验证了 add 方法需要被调用两次
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
import org.junit.Test; public class CallTimesTest {
@Test
public void verifyBehavior1() {
List<Integer> mock = mock(List.class);
mock.add(1);
mock.add(1);
verify(mock, times(2)).add(1);
}
}
3.7 预设 Exception
下面代码演示了预设 Exceptio 发生
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.Test; public class ExceptionTest {
@Test(expected = IOException.class)
public void when_thenThrow() throws IOException {
OutputStream outputStream = mock(OutputStream.class);
doThrow(new IOException()).when(outputStream).close();
outputStream.close();
}
}
代码说明如下:
- @Test(expected = IOException.class) 表示该测试需要有 IOException 抛出
- doThrow 表示指定操作将抛出指定异常
3.8 Void Mock
前面的 thenReturn 只适用于有返回值的方法,本例讲述如何 Mock void 方法
声明服务类如下
package com.lld.test.mockito; import org.springframework.stereotype.Component; @Component
public class VoidService {
public void sayHi(String name) {
System.out.println("Hello, " + name);
}
}
测试类如下所示
package com.lld.test.mockito; import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class VoidTest {
@Mock
VoidService voidService; @Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
} @Test
public void voidTest() {
doNothing().when(voidService).sayHi(anyString());
voidService.sayHi("Lindong");
verify(voidService, times(1)).sayHi(anyString());
} @Test
public void voidArgumentTest() {
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
doNothing().when(voidService).sayHi(argumentCaptor.capture());
voidService.sayHi("Lindong");
assertEquals("Lindong", argumentCaptor.getValue());
} @Test
public void answerTest() {
doAnswer(answer -> {
String name = answer.getArgumentAt(0, String.class);
System.out.println("invoke VoidService with argument: " + name);
return null;
}).when(voidService).sayHi(anyString());
voidService.sayHi("Lindong");
}
}
代码说明如下
- voidTest 演示了如何简单地 Mock void 方法
- voidArgumentTest 演示了如何获取 void 方法的参数
- answerTest 演示了如何截获 void 调用
3.9 级联 Mock
本例演示了如何自动 Mock 所有对象下的子对象
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import org.junit.Test; public class DeepMockTest {
@Test
public void deepstubsAutoTest() {
Account account = mock(Account.class, RETURNS_DEEP_STUBS);
when(account.getRailwayTicket().getDestination()).thenReturn("Beijing");
account.getRailwayTicket().getDestination();
verify(account.getRailwayTicket()).getDestination();
assertEquals("Beijing", account.getRailwayTicket().getDestination());
} @Test
public void deepstubsManualTest() {
Account account = mock(Account.class);
RailwayTicket railwayTicket = mock(RailwayTicket.class);
when(account.getRailwayTicket()).thenReturn(railwayTicket);
when(railwayTicket.getDestination()).thenReturn("Beijing"); account.getRailwayTicket().getDestination();
verify(account.getRailwayTicket()).getDestination();
assertEquals("Beijing", account.getRailwayTicket().getDestination());
} public class RailwayTicket {
private String destination; public String getDestination() {
return destination;
} public void setDestination(String destination) {
this.destination = destination;
}
} public class Account {
private RailwayTicket railwayTicket; public RailwayTicket getRailwayTicket() {
return railwayTicket;
} public void setRailwayTicket(RailwayTicket railwayTicket) {
this.railwayTicket = railwayTicket;
}
}
}
代码说明如下:
- deepstubsAutoTest 演示了自动创建子对象的 Mock (推荐)
- deepstubsManualTest 演示了手动创建子对象的 Mock
3.10 部分 Mock
如下例所示,我们需要使用 Mock 跳过 Exception
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy; import org.junit.Test; public class PartialMockTest { @Test
public void partialMockTest() throws Exception {
TestObj mockObj = spy(new TestObj());
doNothing().when(mockObj).m1();
mockObj.m3();
} class TestObj {
public void m1() throws Exception {
throw new Exception("exception");
} public void m2() {
System.out.println("m2 is invoked");
} public void m3() throws Exception {
m1();
m2();
}
}
}
我们使用了 spy 方法,它返回的对象是一个真实的对象,所有的方法调用也都是真的方法调用。但像例子中演示的,可以 Mock 掉指定的方法。如果有返回值,也可以和以前的例子一样使用 thenReturn。
4 FAQ
4.1 注意点
对于 @Mock 标注,MockitoAnnotations.initMocks(this); 一定要放在第一行
Mock 对象的所有方法均为假方法,而不是默认实现
5 References
Java Mockito 笔记的更多相关文章
- 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁
什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...
- 0035 Java学习笔记-注解
什么是注解 注解可以看作类的第6大要素(成员变量.构造器.方法.代码块.内部类) 注解有点像修饰符,可以修饰一些程序要素:类.接口.变量.方法.局部变量等等 注解要和对应的配套工具(APT:Annot ...
- Java学习笔记(04)
Java学习笔记(04) 如有不对或不足的地方,请给出建议,谢谢! 一.对象 面向对象的核心:找合适的对象做合适的事情 面向对象的编程思想:尽可能的用计算机语言来描述现实生活中的事物 面向对象:侧重于 ...
- 0032 Java学习笔记-类加载机制-初步
JVM虚拟机 Java虚拟机有自己完善的硬件架构(处理器.堆栈.寄存器等)和指令系统 Java虚拟机是一种能运行Java bytecode的虚拟机 JVM并非专属于Java语言,只要生成的编译文件能匹 ...
- 0030 Java学习笔记-面向对象-垃圾回收、(强、软、弱、虚)引用
垃圾回收特点 垃圾:程序运行过程中,会为对象.数组等分配内存,运行过程中或结束后,这些对象可能就没用了,没有变量再指向它们,这时候,它们就成了垃圾,等着垃圾回收程序的回收再利用 Java的垃圾回收机制 ...
- 0028 Java学习笔记-面向对象-Lambda表达式
匿名内部类与Lambda表达式示例 下面代码来源于:0027 Java学习笔记-面向对象-(非静态.静态.局部.匿名)内部类 package testpack; public class Test1{ ...
- 0025 Java学习笔记-面向对象-final修饰符、不可变类
final关键字可以用于何处 修饰类:该类不可被继承 修饰变量:该变量一经初始化就不能被重新赋值,即使该值跟初始化的值相同或者指向同一个对象,也不可以 类变量: 实例变量: 形参: 注意可以修饰形参 ...
- [Java入门笔记] 面向对象编程基础(二):方法详解
什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...
- 《Java学习笔记(第8版)》学习指导
<Java学习笔记(第8版)>学习指导 目录 图书简况 学习指导 第一章 Java平台概论 第二章 从JDK到IDE 第三章 基础语法 第四章 认识对象 第五章 对象封装 第六章 继承与多 ...
随机推荐
- 使用jave1.0.2将amr文件转成其他格式报错解决方案
背景:最近需要将微信公众号里面用户发的语音文件转成其他格式的语音文件 介绍:在刚开始使用jave1.0.2 没有几行代码就可以实现,但是发现在转换的过程会报错,但是最后文件也转成功了,此时是在wind ...
- MySQL数据物理备份之xtrabackup
percona-xtrabackup 它是开源免费的支持MySQL 数据库热备份的软件,它能对InnoDB和XtraDB存储引擎的数据库非阻塞地备份.它不暂停服务创建Innodb热备份: 为mysql ...
- mysql系列1
1. mysql数据库的安装步骤如下: [root@mysqltest01 local]# pwd/usr/local [root@mysqltest01 local]# tar -xvf mysql ...
- nginx常用伪静态设置
nginx里使用伪静态是直接在nginx.conf 中写规则的,并不需要像apache要开启写模块(mod_rewrite)才能进行伪静态. nginx只需要打开nginx.conf配置文件,在ser ...
- MySQL服务的构成(二)
一.什么是实例 这里的实例不是类产生的实例对象,而是Linux系统下的一种机制 1.MySQL的后台进程+线程+预分配的内存结构. 2.MySQL在启动的过程中会启动后台守护进程,并生成工作线程,预分 ...
- 20180606模拟赛T4——数学游戏
数学游戏 题目描述: 小T又发脑残了,没错,她又要求奇怪的东西,这次她想知道[X,Y]之间整数有多少可以表示成K个不同的B的幂的和形势.如\(x,y,k,b=15,20,2,2\),则有: \[17= ...
- 10-C#笔记-封装
基本的封装同C++类似 using System; namespace RectangleApplication { class Rectangle { //成员变量 internal double ...
- redisTemplate 键值序列化策略
redisTemplate 键值序列化策略 RedisSerializer<T> StringRedisSerializer JdkSerializationRedisSerializer ...
- ESA2GJK1DH1K基础篇: STM32+Wi-Fi(AT指令版)实现MQTT源码讲解
前言 注: 本程序发送心跳包,发送温湿度,返回控制数据这三个发送是单独的,有可能凑到一起发. 由于本身程序就是复杂性的程序,所以这节程序没有使用中断发送,没有使用环形队列发送,为了避免多条消息可能凑到 ...
- selenium--浏览器窗口截图
前戏 在进行web自动化的时候,只有一个报错信息是不行的,往往需要截图来帮助我们来快速的定位问题,试想一下,我们在一个弹框里添加一些数据,点击保存后,然后在操作元素,这时selenium报错,说找不到 ...