Mockito 是一个模拟测试框架。主要功能是模拟类/对象的行为。
入门
Mockito 是一个模拟测试框架。主要功能是模拟类/对象的行为。
Mockito 一般用于控制调用外部的返回值,让我们只关心和测试自己的业务逻辑。
我们看一个示例:
package demo;
import java.util.Random;
public class HttpService {
public int queryStatus() {
// 发起网络请求,提取返回结果
// 这里用随机数模拟结果
return new Random().nextInt(2);
}
}
package demo;
public class ExampleService {
private HttpService httpService;
public void setHttpService(HttpService httpService) {
this.httpService = httpService;
}
public String hello() {
int status = httpService.queryStatus();
if (status == 0) {
return "你好";
}
else if (status == 1) {
return "Hello";
}
else {
return "未知状态";
}
}
}
使用示例:
package demo;
public class Main {
public static void main(String[] args) {
HttpService realHttpService = new HttpService();
ExampleService exampleService = new ExampleService();
exampleService.setHttpService(realHttpService);
System.out.println( exampleService.hello() );
}
}
每次运行的结果可能不同,可能是你好
,也可能是Hello
。
现在我们引入 mockito。如果是用 gradle 构建 gradle 项目,加入以下依赖:
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
}
注意,我们引入了 junit 来编写断言。断言是测试的核心。
整个项目结构如下:
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ ├── HttpService.java
│ │ ├── Main.java
│ │ └── ExampleService.java
│ └── resources
└── test
├── java
│ └── demo
│ └── ExampleServiceTest.java
└── resources
其中 test 目录下的 ExampleServiceTest 类,是我们新增的测试类:
package demo;
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ExampleServiceTest {
@Test
public void test() {
// 创建mock对象
HttpService mockHttpService = mock(HttpService.class);
// 使用 mockito 对 queryStatus 方法打桩
when(mockHttpService.queryStatus()).thenReturn(1);
// 调用 mock 对象的 queryStatus 方法,结果永远是 1
Assert.assertEquals(1, mockHttpService.queryStatus());
ExampleService exampleService = new ExampleService();
exampleService.setHttpService(mockHttpService);
Assert.assertEquals("Hello", exampleService.hello() );
}
}
我们通过 mock 函数生成了一个 HttpService 的 mock 对象(这个对象是动态生成的)。
通过 when .. thenReturn 指定了当调用 mock对象的 queryStatus 方法时,返回 1 ,这个叫做打桩
。
然后将 mock 对象注入到 exampleService 中,exampleService.hello() 的返回永远是 Hello
。
引入 Mockito 依赖包
如果是用 gradle 构建 gradle 项目,加入以下依赖:
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
}
注意,这里额外引入了 junit 来编写断言。断言是测试的核心。
使用 mock 方法模拟类和接口
org.mockito.Mockito 的 mock 方法可以模拟类和接口。
mock 类:
import org.junit.Assert;
import org.junit.Test;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenReturn(100); // 指定调用 nextInt 方法时,永远返回 100
Assert.assertEquals(100, mockRandom.nextInt());
Assert.assertEquals(100, mockRandom.nextInt());
}
}
注意,mock 对象的方法的返回值默认都是返回类型的默认值
。例如,返回类型是 int,默认返回值是 0;返回类型是一个类,默认返回值是 null。
import org.junit.Assert;
import org.junit.Test;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
Random mockRandom = mock(Random.class);
System.out.println( mockRandom.nextBoolean() );
System.out.println( mockRandom.nextInt() );
System.out.println( mockRandom.nextDouble() );
}
}
运行 test 方法,输出:
false
0
0.0
mock 接口:
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
List mockList = mock(List.class);
Assert.assertEquals(0, mockList.size());
Assert.assertEquals(null, mockList.get(0));
mockList.add("a"); // 调用 mock 对象的写方法,是没有效果的
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
Assert.assertEquals(null, mockList.get(0)); // 没有指定 get(0) 返回值,这里结果是默认值
when(mockList.get(0)).thenReturn("a"); // 指定 get(0)时返回 a
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
Assert.assertEquals("a", mockList.get(0)); // 因为上面指定了 get(0) 返回 a,所以这里会返回 a
Assert.assertEquals(null, mockList.get(1)); // 没有指定 get(1) 返回值,这里结果是默认值
}
}
@Mock 注解
@Mock 注解可以理解为对 mock 方法的一个替代。
使用该注解时,要使用MockitoAnnotations.initMocks
方法,让注解生效。
示例1:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Mock
private Random random;
@Test
public void test() {
// 让注解生效
MockitoAnnotations.initMocks(this);
when(random.nextInt()).thenReturn(100);
Assert.assertEquals(100, random.nextInt());
}
}
MockitoAnnotations.initMocks
放在 junit 的 @Before
注解修饰的函数中更合适。
示例2:
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Mock
private Random random;
@Before
public void before() {
// 让注解生效
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
when(random.nextInt()).thenReturn(100);
Assert.assertEquals(100, random.nextInt());
}
}
MockitoAnnotations.initMocks 的一个替代方案是使用 MockitoJUnitRunner 。
示例3:
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Random;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {
@Mock
private Random random;
@Test
public void test() {
when(random.nextInt()).thenReturn(100);
Assert.assertEquals(100, random.nextInt());
}
}
mock 泛型类、泛型接口
示例:
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
ArrayList<String> mockList = mock(ArrayList.class); // 这种写法不够精确,IDE也会警告
when(mockList.get(0)).thenReturn("abc");
Assert.assertEquals(3, mockList.get(0).length());
}
}
下面这种用 @Mock 注解的方法,IDE 不会警告:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Mock
private ArrayList<String> mockList;
@Test
public void test() {
MockitoAnnotations.initMocks(this);
when(mockList.get(0)).thenReturn("abc");
Assert.assertEquals(3, mockList.get(0).length());
}
}
参数匹配
精确匹配
我们之前介绍过这样的例子:
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
List mockList = mock(List.class);
Assert.assertEquals(0, mockList.size());
Assert.assertEquals(null, mockList.get(0));
mockList.add("a"); // 调用 mock 对象的写方法,是没有效果的
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
Assert.assertEquals(null, mockList.get(0)); // 没有指定 get(0) 返回值,这里结果是默认值
when(mockList.get(0)).thenReturn("a"); // 指定 get(0)时返回 a
Assert.assertEquals(0, mockList.size()); // 没有指定 size() 方法返回值,这里结果是默认值
Assert.assertEquals("a", mockList.get(0)); // 因为上面指定了 get(0) 返回 a,所以这里会返回 a
Assert.assertEquals(null, mockList.get(1)); // 没有指定 get(1) 返回值,这里结果是默认值
}
}
其中when(mockList.get(0)).thenReturn("a");
指定了get(0)
的返回值,这个 0 就是参数的精确匹配。我们还可以让不同的参数对应不同的返回值,例如:
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Mock
private List<String> mockStringList;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
mockStringList.add("a");
when(mockStringList.get(0)).thenReturn("a");
when(mockStringList.get(1)).thenReturn("b");
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("b", mockStringList.get(1));
}
}
对于精确匹配,还可以用 eq,例如:
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Mock
private List<String> mockStringList;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
mockStringList.add("a");
when(mockStringList.get(eq(0))).thenReturn("a"); // 虽然可以用eq进行精确匹配,但是有点多余
when(mockStringList.get(eq(1))).thenReturn("b");
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("b", mockStringList.get(1));
}
}
模糊匹配
可以使用 Mockito.anyInt()
匹配所有类型为 int 的参数:
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Mock
private List<String> mockStringList;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void test() {
mockStringList.add("a");
when(mockStringList.get(anyInt())).thenReturn("a"); // 使用 Mockito.anyInt() 匹配所有的 int
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("a", mockStringList.get(1));
}
}
anyInt 只是用来匹配参数的工具之一,目前 mockito 有多种匹配函数,部分如下:
函数名 | 匹配类型 |
---|---|
any() | 所有对象类型 |
anyInt() | 基本类型 int、非 null 的 Integer 类型 |
anyChar() | 基本类型 char、非 null 的 Character 类型 |
anyShort() | 基本类型 short、非 null 的 Short 类型 |
anyBoolean() | 基本类型 boolean、非 null 的 Boolean 类型 |
anyDouble() | 基本类型 double、非 null 的 Double 类型 |
anyFloat() | 基本类型 float、非 null 的 Float 类型 |
anyLong() | 基本类型 long、非 null 的 Long 类型 |
anyByte() | 基本类型 byte、非 null 的 Byte 类型 |
anyString() | String 类型(不能是 null) |
anyList() | List<T> 类型(不能是 null) |
anyMap() | Map<K, V> 类型(不能是 null) |
anyCollection() | Collection<T> 类型(不能是 null) |
anySet() | Set<T> 类型(不能是 null) |
any(Class<T> type) |
type类型的对象(不能是 null) |
isNull() | null |
notNull() | 非 null |
isNotNull() | 非 null |
参数匹配顺序
如果参数匹配即生命了精确匹配,也声明了模糊匹配;又或者同一个值的精确匹配出现了两次,使用时会匹配哪一个?
会匹配符合匹配条件的最新声明的匹配。
示例:
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {
@Mock
private List<String> testList;
@Test
public void test01() {
// 精确匹配 0
when(testList.get(0)).thenReturn("a");
Assert.assertEquals("a", testList.get(0));
// 精确匹配 0
when(testList.get(0)).thenReturn("b");
Assert.assertEquals("b", testList.get(0));
// 模糊匹配
when(testList.get(anyInt())).thenReturn("c");
Assert.assertEquals("c", testList.get(0));
Assert.assertEquals("c", testList.get(1));
}
@Test
public void test02() {
// 模糊匹配
when(testList.get(anyInt())).thenReturn("c");
Assert.assertEquals("c", testList.get(0));
Assert.assertEquals("c", testList.get(1));
// 精确匹配 0
when(testList.get(0)).thenReturn("a");
Assert.assertEquals("a", testList.get(0));
Assert.assertEquals("c", testList.get(1));
}
}
spy 和 @Spy 注解
spy 和 mock不同,不同点是:
- spy 的参数是对象示例,mock 的参数是 class。
- 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
下面是一个对比:
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
class ExampleService {
int add(int a, int b) {
return a+b;
}
}
public class MockitoDemo {
// 测试 spy
@Test
public void test_spy() {
ExampleService spyExampleService = spy(new ExampleService());
// 默认会走真实方法
Assert.assertEquals(3, spyExampleService.add(1, 2));
// 打桩后,不会走了
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 2));
// 但是参数比匹配的调用,依然走真实方法
Assert.assertEquals(3, spyExampleService.add(2, 1));
}
// 测试 mock
@Test
public void test_mock() {
ExampleService mockExampleService = mock(ExampleService.class);
// 默认返回结果是返回类型int的默认值
Assert.assertEquals(0, mockExampleService.add(1, 2));
}
}
spy 对应注解 @Spy,和 @Mock 是一样用的。
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import static org.mockito.Mockito.*;
class ExampleService {
int add(int a, int b) {
return a+b;
}
}
public class MockitoDemo {
@Spy
private ExampleService spyExampleService;
@Test
public void test_spy() {
MockitoAnnotations.initMocks(this);
Assert.assertEquals(3, spyExampleService.add(1, 2));
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 2));
}
}
对于@Spy
,如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。
所以下面两种写法是等价的:
// 写法1
@Spy
private ExampleService spyExampleService;
// 写法2
@Spy
private ExampleService spyExampleService = new ExampleService();
如果没有无参构造函数,必须使用写法2。例子:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
class ExampleService {
private int a;
public ExampleService(int a) {
this.a = a;
}
int add(int b) {
return a+b;
}
}
public class MockitoDemo {
@Spy
private ExampleService spyExampleService = new ExampleService(1);
@Test
public void test_spy() {
MockitoAnnotations.initMocks(this);
Assert.assertEquals(3, spyExampleService.add(2));
}
}
使用 @InjectMocks 注解注入 mock 对象
mockito 会将 @Mock
、@Spy
修饰的对象自动注入到 @InjectMocks
修饰的对象中。
注入方式有多种,mockito 会按照下面的顺序尝试注入:
- 构造函数注入
- 设值函数注入(set函数)
- 属性注入
示例:
准备两个业务类:
package demo;
import java.util.Random;
public class HttpService {
public int queryStatus() {
// 发起网络请求,提取返回结果
// 这里用随机数模拟结果
return new Random().nextInt(2);
}
}
package demo;
public class ExampleService {
private HttpService httpService;
public String hello() {
int status = httpService.queryStatus();
if (status == 0) {
return "你好";
}
else if (status == 1) {
return "Hello";
}
else {
return "未知状态";
}
}
}
编写测试类:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.when;
public class ExampleServiceTest {
@Mock
private HttpService httpService;
@InjectMocks
private ExampleService exampleService = new ExampleService(); // 会将 httpService 注入进去
@Test
public void test01() {
MockitoAnnotations.initMocks(this);
when(httpService.queryStatus()).thenReturn(0);
Assert.assertEquals("你好", exampleService.hello());
}
}
使用 thenReturn 设置方法的返回值
thenReturn 用来指定特定函数和参数调用的返回值。
比如:
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
import java.util.Random;
public class MockitoDemo {
@Test
public void test() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenReturn(1);
Assert.assertEquals(1, mockRandom.nextInt());
}
}
thenReturn 中可以指定多个返回值。在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
import java.util.Random;
public class MockitoDemo {
@Test
public void test() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenReturn(1, 2, 3);
Assert.assertEquals(1, mockRandom.nextInt());
Assert.assertEquals(2, mockRandom.nextInt());
Assert.assertEquals(3, mockRandom.nextInt());
Assert.assertEquals(3, mockRandom.nextInt());
Assert.assertEquals(3, mockRandom.nextInt());
}
}
使用 thenThrow 让方法抛出异常
thenThrow 用来让函数调用抛出异常。
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
import java.util.Random;
public class MockitoDemo {
@Test
public void test() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));
try {
mockRandom.nextInt();
Assert.fail(); // 上面会抛出异常,所以不会走到这里
} catch (Exception ex) {
Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertEquals("异常", ex.getMessage());
}
}
}
thenThrow 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
import java.util.Random;
public class MockitoDemo {
@Test
public void test() {
Random mockRandom = mock(Random.class);
when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));
try {
mockRandom.nextInt();
Assert.fail();
} catch (Exception ex) {
Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertEquals("异常1", ex.getMessage());
}
try {
mockRandom.nextInt();
Assert.fail();
} catch (Exception ex) {
Assert.assertTrue(ex instanceof RuntimeException);
Assert.assertEquals("异常2", ex.getMessage());
}
}
}
对应返回类型是 void 的函数,thenThrow 是无效的,要使用 Mockito 使用 doThrow 让方法抛出异常。
使用then、thenAnswer 自定义方法处理逻辑
then 和 thenAnswer 的效果是一样的。它们的参数是实现 Answer 接口的对象,在改对象中可以获取调用参数,自定义返回值。
示例:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Mock
private ExampleService exampleService;
@Test
public void test() {
MockitoAnnotations.initMocks(this);
when(exampleService.add(anyInt(),anyInt())).thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
// 获取参数
Integer a = (Integer) args[0];
Integer b = (Integer) args[1];
// 根据第1个参数,返回不同的值
if (a == 1) {
return 9;
}
if (a == 2) {
return 99;
}
if (a == 3) {
throw new RuntimeException("异常");
}
return 999;
}
});
Assert.assertEquals(9, exampleService.add(1, 100));
Assert.assertEquals(99, exampleService.add(2, 100));
try {
exampleService.add(3, 100);
Assert.fail();
} catch (RuntimeException ex) {
Assert.assertEquals("异常", ex.getMessage());
}
}
}
使用 doReturn 设置方法的返回值
doReturn 的作用和 thenReturn (Mockito 使用 thenReturn 设置方法的返回值) 相同,但使用方式不同:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
MockitoAnnotations.initMocks(this);
Random random = mock(Random.class);
doReturn(1).when(random).nextInt();
Assert.assertEquals(1, random.nextInt());
}
}
使用 doThrow 让方法抛出异常
如果一个对象的方法的返回值是 void,那么不能用 when .. thenThrow 让该方法抛出异常
如果有返回值,
下面这种写法是错误的:
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.when;
public class MockitoDemo {
static class ExampleService {
public void hello() {
System.out.println("Hello");
}
}
@Mock
private ExampleService exampleService;
@Test
public void test() {
MockitoAnnotations.initMocks(this);
// 这句编译不通过,IDE 也会提示错误,原因很简单,when 的参数是非 void
when(exampleService.hello()).thenThrow(new RuntimeException("异常"));
}
}
用 doThrow 可以让返回void的函数抛出异常
换成下面的写法即可:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.mockito.Mockito.doThrow;
public class MockitoDemo {
static class ExampleService {
public void hello() {
System.out.println("Hello");
}
}
@Mock
private ExampleService exampleService;
@Test
public void test() {
MockitoAnnotations.initMocks(this);
// 这种写法可以达到效果
doThrow(new RuntimeException("异常")).when(exampleService).hello();
try {
exampleService.hello();
Assert.fail();
} catch (RuntimeException ex) {
Assert.assertEquals("异常", ex.getMessage());
}
}
}
也可以用 doThrow 让返回非void的函数抛出异常
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
MockitoAnnotations.initMocks(this);
Random random = mock(Random.class);
// 下面这句等同于 when(random.nextInt()).thenThrow(new RuntimeException("异常"));
doThrow(new RuntimeException("异常")).when(random).nextInt();
try {
random.nextInt();
Assert.fail();
} catch (RuntimeException ex) {
Assert.assertEquals("异常", ex.getMessage());
}
}
}
使用 doAnswer 自定义方法处理逻辑
doAnswer 的作用和 thenAnswer (Mockito 使用then、thenAnswer 自定义方法处理逻辑)相同,但使用方式不同:
import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Random;
import static org.mockito.Mockito.*;
public class MockitoDemo {
@Test
public void test() {
MockitoAnnotations.initMocks(this);
Random random = mock(Random.class);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return 1;
}
}).when(random).nextInt();
Assert.assertEquals(1, random.nextInt());
}
}
使用 doNothing 让 void 函数什么都不做
doNothing 用于让 void 函数什么都不做。因为 mock 对象中,void 函数就是什么都不做,所以该方法更适合 spy 对象。
示例:
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public void hello() {
System.out.println("Hello");
}
}
@Test
public void test() {
ExampleService exampleService = spy(new ExampleService());
exampleService.hello(); // 会输出 Hello
// 让 hello 什么都不做
doNothing().when(exampleService).hello();
exampleService.hello(); // 什么都不输出
}
}
使用 MockitoJUnitRunner 运行 JUnit 测试
见 Mockito @Mock 注解 。主要作用是让 @Mock、@Spy 等注解生效。
使用 MockitoAnnotations.initMocks 让 @Mock 等注解生效
见 Mockito @Mock 注解 。
使用 reset 重置对象
使用 reset 方法,可以重置之前自定义的返回值和异常。
reset mock 对象示例
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// mock 对象方法的默认返回值是返回类型的默认值
Assert.assertEquals(0, exampleService.add(1, 2));
// 设置让 add(1,2) 返回 100
when(exampleService.add(1, 2)).thenReturn(100);
Assert.assertEquals(100, exampleService.add(1, 2));
// 重置 mock 对象,add(1,2) 返回 0
reset(exampleService);
Assert.assertEquals(0, exampleService.add(1, 2));
}
}
reset spy 对象示例
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = spy(new ExampleService());
// spy 对象方法调用会用真实方法,所以这里返回 3
Assert.assertEquals(3, exampleService.add(1, 2));
// 设置让 add(1,2) 返回 100
when(exampleService.add(1, 2)).thenReturn(100);
Assert.assertEquals(100, exampleService.add(1, 2));
// 重置 spy 对象,add(1,2) 返回 3
reset(exampleService);
Assert.assertEquals(3, exampleService.add(1, 2));
}
}
使用 thenCallRealMethod 调用 spy 对象的真实方法
thenCallRealMethod 可以用来重置 spy 对象的特定方法特定参数调用。
示例:
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = spy(new ExampleService());
// spy 对象方法调用会用真实方法,所以这里返回 3
Assert.assertEquals(3, exampleService.add(1, 2));
// 设置让 add(1,2) 返回 100
when(exampleService.add(1, 2)).thenReturn(100);
when(exampleService.add(2, 2)).thenReturn(100);
Assert.assertEquals(100, exampleService.add(1, 2));
Assert.assertEquals(100, exampleService.add(2, 2));
// 重置 spy 对象,让 add(1,2) 调用真实方法,返回 3
when(exampleService.add(1, 2)).thenCallRealMethod();
Assert.assertEquals(3, exampleService.add(1, 2));
// add(2, 2) 还是返回 100
Assert.assertEquals(100, exampleService.add(2, 2));
}
}
使用 verify 校验是否发生过某些操作
使用 verify 可以校验 mock 对象是否发生过某些操作
示例
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// 设置让 add(1,2) 返回 100
when(exampleService.add(1, 2)).thenReturn(100);
exampleService.add(1, 2);
// 校验是否调用过 add(1, 2) -> 校验通过
verify(exampleService).add(1, 2);
// 校验是否调用过 add(2, 2) -> 校验不通过
verify(exampleService).add(2, 2);
}
}
verify 配合 time 方法,可以校验某些操作发生的次数
示例:
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// 第1次调用
exampleService.add(1, 2);
// 校验是否调用过一次 add(1, 2) -> 校验通过
verify(exampleService, times(1)).add(1, 2);
// 第2次调用
exampleService.add(1, 2);
// 校验是否调用过两次 add(1, 2) -> 校验通过
verify(exampleService, times(2)).add(1, 2);
}
}
使用 mockingDetails 方法判断对象是否为 mock对象、spy 对象
Mockito 的 mockingDetails 方法会返回 MockingDetails 对象,它的 isMock 方法可以判断对象是否为 mock 对象,isSpy 方法可以判断对象是否为 spy 对象。
示例:
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// 判断 exampleService 是否为 mock 对象
System.out.println( mockingDetails(exampleService).isMock() ); // true
// 判断 exampleService 是否为 spy 对象
System.out.println( mockingDetails(exampleService).isSpy() ); // false
}
}
链式调用
thenReturn、doReturn 等函数支持链式调用,用来指定函数特定调用次数时的行为。
示例1:
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// 让第1次调用返回 100,第2次调用返回 200
when(exampleService.add(1, 2)).thenReturn(100).thenReturn(200);
Assert.assertEquals(100, exampleService.add(1, 2));
Assert.assertEquals(200, exampleService.add(1, 2));
Assert.assertEquals(200, exampleService.add(1, 2));
}
}
import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Test
public void test() {
ExampleService exampleService = mock(ExampleService.class);
// 让第1次调用返回 100,第2次调用返回 200
doReturn(100).doReturn(200).when(exampleService).add(1, 2);
Assert.assertEquals(100, exampleService.add(1, 2));
Assert.assertEquals(200, exampleService.add(1, 2));
Assert.assertEquals(200, exampleService.add(1, 2));
}
}
测试隔离
根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。
比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。
示例:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {
static class ExampleService {
public int add(int a, int b) {
return a+b;
}
}
@Mock
private ExampleService exampleService;
@Test
public void test01() {
System.out.println("---call test01---");
System.out.println("打桩前: " + exampleService.add(1, 2));
when(exampleService.add(1, 2)).thenReturn(100);
System.out.println("打桩后: " + exampleService.add(1, 2));
}
@Test
public void test02() {
System.out.println("---call test02---");
System.out.println("打桩前: " + exampleService.add(1, 2));
when(exampleService.add(1, 2)).thenReturn(100);
System.out.println("打桩后: " + exampleService.add(1, 2));
}
}
将两个单测一起运行,运行结果是:
---call test01---
打桩前: 0
打桩后: 100
---call test02---
打桩前: 0
打桩后: 100
test01 先被执行,打桩前调用add(1, 2)
的结果是0,打桩后是 100。
然后 test02 被执行,打桩前调用add(1, 2)
的结果是0,而非 100,这证明了我们上面的说法。
使用 PowerMock 让 Mockito 支持静态方法
PowerMock 是一个增强库,用来增加 Mockito 、EasyMock 等测试库的功能。
Mockito 默认是不支持静态方法
比如我们在 ExampleService 类中定义静态方法 add:
public class ExampleService {
public static int add(int a, int b) {
return a+b;
}
}
尝试给静态方法打桩,会报错:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {
@Test
public void test() {
// 会报错
when(ExampleService.add(1, 2)).thenReturn(100);
}
}
可以用 Powermock 弥补 Mockito 缺失的静态方法 mock 功能
在 build.gradle 中配置以下依赖:
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
// PowerMock 相关依赖
testCompile group: 'org.powermock', name: 'powermock-core', version: '2.0.0'
testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.0'
testCompile group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.0'
}
示例:
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.*;
@RunWith(PowerMockRunner.class) // 这是必须的
@PrepareForTest(ExampleService.class) // 声明要处理 ExampleService
public class MockitoDemo {
@Test
public void test() {
PowerMockito.mockStatic(ExampleService.class); // 这也是必须的
when(ExampleService.add(1, 2)).thenReturn(100);
Assert.assertEquals(100, ExampleService.add(1, 2));
Assert.assertEquals(0, ExampleService.add(2, 2));
}
}
PowerMockRunner 支持 Mockito 的 @Mock 等注解
上面我们用了 PowerMockRunner ,MockitoJUnitRunner 就不能用了。但不要担心, @Mock 等注解还能用。
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.Random;
import static org.mockito.Mockito.*;
@RunWith(PowerMockRunner.class)
public class MockitoDemo {
@Mock
private Random random;
@Test
public void test() {
when(random.nextInt()).thenReturn(1);
Assert.assertEquals(1, random.nextInt());
}
}
如何临时 mock 对象
如果需要临时将一个对象的内部对象替换为 mock 对象,在无法通过set和get处理内部对象的情况下,可以利用反射搞定。
Java JOOR 反射库 是一个很好用的反射库。本文用它进行临时替换。
用一个小项目作为示例:
项目结构:
.
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ ├── HttpService.java
│ │ └── BizService.java
│ └── resources
└── test
├── java
│ └── demo
│ └── BizServiceTest.java
└── resources
build.gradle 中声明的依赖:
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
testCompile group: 'org.jooq', name: 'joor-java-8', version: '0.9.7'
}
HttpService 类:
package demo;
public class HttpService {
public int queryStatus() {
// 发起网络请求,提取返回结果
// 这里直接返回0
return 0;
}
}
BizService 类:
package demo;
public class BizService {
private HttpService httpService = new HttpService();
public String hello() {
int status = httpService.queryStatus();
if (status == 0) {
return "你好";
}
else if (status == 1) {
return "Hello";
}
else {
return "未知状态";
}
}
}
BizServiceTest 测试类:
package demo;
import org.joor.Reflect;
import org.junit.Test;
import static org.mockito.Mockito.*;
public class BizServiceTest {
private BizService bizService = new BizService();
@Test
public void testHello() {
System.out.println( bizService.hello() ); // 输出'你好'
// 取出原有的对象
Object realHttpService = Reflect.on(bizService).get("httpService");
// 创建 mock 对象,并用它替换掉 bizService 中的 httpService 对象
HttpService mockHttpService = mock(HttpService.class);
when(mockHttpService.queryStatus()).thenReturn(1);
Reflect.on(bizService).set("httpService", mockHttpService);
System.out.println( bizService.hello() ); // 输出'hello'
// 再将原先的对象设置回去
Reflect.on(bizService).set("httpService", realHttpService);
System.out.println( bizService.hello() ); // 输出'你好'
}
}