Mockito 是一个模拟测试框架,主要功能是在单元测试中模拟类/对象的行为。
1 为什么要使用Mockito?
Mock可以理解为创建一个虚假的对象,或者说模拟出一个对象.在测试环境中用来替换掉真实的对象,以达到我们可以
- 验证该对象的某些方法的调用情况,调用了多少次,参数是多少.
- 给这个对象的行为做一个定义,来指定返回结果或指定特定的动作.
2 Mockito数据隔离
根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。
比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。
3 Mock方法
mock(Class classToMock)
mock方法来自org.mockito.Mock
,它标识可以mock一个对象或者是接口
public static <T> mock(Class<T> classToMock);
- 入参:classToMock: 待mock对象的class类
- 返回:mock出来的类的对象(此时对象内依旧无数据)
Random random = Mockito.mock(Random.class);
Stub 存根 : 为mock对象规定预期的目标执行结果
简述
存根的意思就是给mock对象规定一行的行为,使其按照我们的要求来执行具体的动作。
样例程序
demo1
//You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);
//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(0)).thenReturn("two");
when(mockedList.get(1)).thenThrow(new RuntimeException());
//following prints "two"
System.out.println(mockedList.get(0));
//following throws runtime exception
System.out.println(mockedList.get(1));
//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
//Although it is possible to verify a stubbed invocation, usually it's just redundant
//If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
//If your code doesn't care what get(0) returns, then it should not be stubbed.
verify(mockedList).get(0);
demo2
@Test
public void test(){
// 创建Mock对象,参数可以是类或者接口
List<String> list = mock(List.class);
// 设置方法的预期返回值
when(list.get(0)).thenReturn("zuozewei");
when(list.get(1)).thenThrow(new RuntimeException("test exception"));
String result = list.get(0);
// 验证方法调用
verify(list).get(0);
//断言,list的第一个元素是否是 "zuozwei"
Assert.assertEquals(result,"zuozewei");
}
常用方法
- when(mockObject.actionMethod).thenReturn(String t) 设置方法的目标返回值
- when(mockObject.actionMethod).thenThrow(Throwable... throwables) 让方法抛出异常
- when(mockObject.actionMethod).thenAnswer(Answer answer) / then(Answer answer) 自定义方法处理逻辑
- when(mockObject.actionMethod).thenCallRealMethod() 调用 spy 对象的真实方法
reset(mockObject) : 重置之前自定义的返回值和异常
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));
}
@Test
public void test2() {
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));
}
}
参数匹配:精确匹配与模糊匹配(anyXXX)
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(mockStringList.get(0)).thenReturn("a");
when(mockStringList.get(1)).thenReturn("b");
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("b", mockStringList.get(1));
// 参数匹配:模糊匹配
when(mockStringList.get(anyInt())).thenReturn("a"); // 使用 Mockito.anyInt() 匹配所有的 int
Assert.assertEquals("a", mockStringList.get(0));
Assert.assertEquals("a", mockStringList.get(1));
}
}
@Mock 注解
@mock快速创建mock的方法,使用
@mock注解需要搭配MockitoAnnotations.openMocks(testClass)方法一起使用.
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@Mock
private ArticleDatabase database;
@Mock
private UserProvider userProvider;
private ArticleManager manager;
@org.junit.jupiter.api.Test
void testSomethingInJunit5() {
// 初始化mock对象
MockitoAnnotations.openMocks(testClass);
//Mockito.mock(class);
Mockito.spy(class);
}
// 简化
// @BeforeEach
// void setUp() {
// MockitoAnnotations.openMocks(this);
// }
}
@Spy注解 与 Spy方法
spy 和 mock不同,不同点是:
- spy 的参数是对象实例,mock 的参数是 class。
- 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
@Spy注解需要搭配MockitoAnnotations.openMocks(testClass)
方法一起使用.
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.openMocks(this);
Assert.assertEquals(3, spyExampleService.add(1, 2));
when(spyExampleService.add(1, 2)).thenReturn(10);
Assert.assertEquals(10, spyExampleService.add(1, 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 {
@InjectMocks // 将httpService主动注入
private ExampleService exampleService = new ExampleService();
@Mock
private HttpService httpService;
@Test
public void test01() {
MockitoAnnotations.initMocks(this);
when(httpService.queryStatus()).thenReturn(0);
Assert.assertEquals("你好", exampleService.hello());
}
}
验证和断言: verify(...)方法
验证是校验待验证的对象是否发生过某些行为,Mockito中验证的方法是:verify
verify(mock).someMoethod("some arg");
verify(mock,times(100)).someMoethod("some arg");
使用verify验证(Junit的断言机制):
@Test
void check() {
Random random = Mockito.mock(Random.class);
System.out.println(random.nextInt());
Mockito.verify(random,Mockito.times(2)).nextInt();
}
Verify配合times()方法,可以校验某些操作发生的次数
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 默认调用1次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
// 自定义调用多次
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// 从不调用
verify(mockedList, never()).add("never happened");
// atLeast()/atMost() 至少调用 / 之多调用
verify(mockedList, atMostOnce()).add("once");
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(5)).add("three times");
// 超时验证
verify(mock, timeout(100)).someMethod();
verify(mock, timeout(100).times(1)).someMethod();
//只要 someMethod() 在 100 毫秒内被调用 2 次,就会通过
verify(mock, timeout(100).times(2)).someMethod();
//someMethod() 至少在 100 毫秒内被调用 2 次,就会通过
verify(mock, timeout(100).atLeast(2)).someMethod();