Mockito
依赖
与JUnit5整合
- 推荐
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
核心包
- 实际很少单独使用mockito,基本都与JUnit联用,如果JUnit版本为5,引入上一个节的包即可
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
验证
验证执行次数
Mockito.verify()方法有两个重载方法<T> T verify(T mock)- 入参仅有一个mock对象,默认表示验证方法执行了一次
<T> T verify(T mock, VerificationMode mode)- 入参有mock对象和一个验证模式对象,根据传入验证模式对象不同,表现不同
- 验证模式
- 所有验证模式类均会实现
VerificationMode接口 - Mockito内置的验证模式可查看VerificationModeFactory类,其生产的对象均为Mockito提供的内置验证模式
Mockito类提供了一些工具方法返回一些验证模式
- 所有验证模式类均会实现
- 以下示例代码及之后所有代码都静态导入了
Mockito类的全部方法,如atMostOnce()mock()spy()实为Mockito.atMostOnce()Mockito.mock()Mockito.spy()
/**
* 验证执行次数
*/
@Test
void testVerifyTimes() {
//mock实例
List mockList = mock(List.class);
//调用方法
mockList.add(1);
mockList.clear();
//验证是否调用了一次
verify(mockList).add(1);
verify(mockList).clear();
//至多1次
verify(mockList, atMostOnce()).add(1);
//至少1次
verify(mockList, atLeastOnce()).add(1);
//调用方法
mockList.add(2);
mockList.add(2);
//验证多次调用
verify(mockList, times(2)).add(2);
//至少2次
verify(mockList, atLeast(2)).add(2);
//至多2次
verify(mockList, atMost(2)).add(2);
//从未被调用
verify(mockList, never()).add(3);
}
顺序验证
- 验证调用顺序是否如同期望的那样,示例代码如下
- 单个mock对象方法的调用顺序
- 多个mock对象方法的调用顺序
- 仅能在顺序调用中表示最少调用次数的验证模式:
Mockito.calls()的返回值Calls
@Test
void testInOrder() {
// A. 验证 mock 一个对象的函数执行顺序
List singleMock = mock(List.class);
// 使用 singleMock
singleMock.add("was added first");
singleMock.add("was added second");
// 为该 mock 对象创建一个 inOrder 对象
InOrder inOrder = inOrder(singleMock);
// 确保 add 函数首先执行的是 add("was added first"),然后才是 add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. 验证多个 mock 对象的函数执行顺序
List firstMock = mock(List.class);
List secondMock = mock(List.class);
// 使用 mock
firstMock.add("was called first");
firstMock.add("was called first");
firstMock.add("was called first");
secondMock.add("was called second");
// 为这两个 mock 对象创建 inOrder 对象
InOrder inOrder2 = inOrder(firstMock, secondMock);
// 验证它们的执行顺序
inOrder2.verify(firstMock,calls(2)).add("was called first");
inOrder2.verify(secondMock).add("was called second");
//PS:calls(2)方法返回的验证模式表示至少2次,该方法仅用于顺序验证
}
确保交互(interaction)操作不会执行在 mock 对象上
- Mockito官方文档中的交互(interactions)指对mock对象方法的调用
Mockito.nerver()返回结果本质是执行次数为0的验证模式
/**
* 验证mock对象没有未被verify的操作
*/
@Test
void testNoInteraction() {
List mockOne = mock(List.class);
List mockTwo = mock(List.class);
List mockThree = mock(List.class);
// 使用 Mock 对象
mockOne.add("one");
mockOne.add(1); //该处调用未被verify验证
// 普通验证
verify(mockOne).add("one");
// 验证某个交互是否从未被执行
verify(mockOne, never()).add("two");
}
查找冗余调用
- 该实例中的API会列出所有交互了的方法并标记出未验证的方法
Mockito.verifyNoMoreInteractions()功能与Mockito.verifyZeroInteractions()()相同Mockito.verifyNoMoreInteractions()为Mockito.verifyZeroInteractions的替代方法
@Test
void testFindRedundant() {
List mockedList = mock(List.class);
// 使用 mock
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");
// 下面的验证将会失败
verifyNoMoreInteractions(mockedList);
// 验证 mock 对象没有交互过
verifyZeroInteractions(mockedList);//过期方法
}
模拟
Mock模拟
编程实现Mockito.mock()
- 返回的mock对象时Mockito根据参数的接口生成的子类
- 为打桩的方法,默认情况下全部返回默认值(null、0、false等)
- 入参字节码对象要求不能为final类
//mock实例
List mockList = Mockito.mock(List.class);
初始化注解环境
@ExtendWith(MockitoExtension.class)注解方式- 写在需要使用到注解字段的方法上
- 写在类上则整个测试类都开启了注解
MockitoAnnotations.initMocks(this)编程方式- 需要在被mock对象调用前执行
@Mock
private List mockList;
@Test
//注解方式
@ExtendWith(MockitoExtension.class)
void testMock() {
//MockitoAnnotations.initMocks(this); 编程方式
//测试代码
}
注解实现@Mock
- 基本功能及特性同
Mockito.mock()
@Mock
private List mockList;
@Test
//注解方式
@ExtendWith(MockitoExtension.class)
void testMock() {
when(mockList.get(0)).thenReturn("haha");
assertEquals("haha", mockList.get(0));
verify(mockList).get(0);
}
真实对象监控Spy
编程实现Mockito.spy()
- 存在两个重载方法
- 入参为字节码对象时
- 底层调用同
Mockito.mock()方法 - 入参要求不能为
final类
- 底层调用同
- 入参为真实对象时
- 未打桩的方法均调用真实方法(打桩概念参见打桩基本概念与简单案例)
- 对象不能为
final类
- 入参为字节码对象时
//接口Class入参,同mock()方法
List spy = Mockito.spy(List.class);
//监控真实对象
List linkedList = new LinkedList();
List spy = Mockito.spy(linkedList);
@Test
void testSpy() {
List list = new LinkedList();
List spy = spy(list);
// 你可以为某些函数打桩
when(spy.size()).thenReturn(100);
// 通过spy对象调用真实对象的函数
spy.add("one");
spy.add("two");
// 输出第一个元素
assertEquals("one", spy.get(0));//未打桩调用真实方法
// 因为size()函数被打桩了,因此这里返回的是100
assertEquals(100, spy.size());
// 交互验证
verify(spy).add("one");
verify(spy).add("two");
}
spy对象打桩注意事项
- 被spy的对象未打桩时会调用真实方法
- 尽量使用
doReturn()系列的方法打桩 when()系列方法中的调用,实际执行时会真的调用,第一次打桩时可能出问题,如下示例
- 尽量使用
@Test
void testSpyStub() {
List list = new LinkedList();
List spy = spy(list);
when(spy.get(0)).thenReturn("foo");// 运行报错 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的
// 你需要使用doReturn()来打桩
doReturn("foo").when(spy).get(0);
assertEquals("foo", spy.get(0));
}
注解实现@Spy
- 若被
@Spy修改的字段未初始化实例,相当于@Mock和编程实现中的Mockito.mock()与Mockito.spy(aClazz) - 若被
@Spy修改的字段已初始化实例,相当于编程中的Mockito.spy(object)
@Mock
private List mockList;
@Test
//注解方式
@ExtendWith(MockitoExtension.class)
void testMock() {
//MockitoAnnotations.initMocks(this); 编程方式
//测试代码
}
真实局部mock
- 同一个mock对象,部分方法mock,部分调用真实方法
- spy对象方式:未打桩方法默认调用真实方法
- mock方式:打桩时设置调用真实方法
/**
* 真实局部mock
*/
@Test
void realPartialMock() {
//you can create partial mock with spy() method:
//使用spy创建真实局部mock对象
List list = spy(new LinkedList());
//you can enable partial mock capabilities selectively on mocks:
//使用mock实现真实局部mock功能
Person mock = mock(Person.class);
//打真实调用桩
doCallRealMethod().when(mock).setName("Bob");
when(mock.getName()).thenCallRealMethod();
//验证
mock.setName("Bob");
assertEquals("Bob", mock.getName());
}
重置mock对象
- 普通用户不推荐使用,容易引入代码以为,提供该方法主要是为了容器测试,而非提供给普通使用者
@Test
void testReset(){
List mock = mock(List.class);
when(mock.size()).thenReturn(10);
mock.add(1);
//重置并清空mock对象所有交互(即调用)和打桩
reset(mock);
}
mock spy 抽象类
- 需要mock的对象为抽象类,需要打桩的方法未实现,且本次打桩关心其实现法时,mockito提供直接mock抽象类的功能
- 要求,抽象类必须有无参构造方法
/**
* mock spy 抽象类
*/
@Test
void testMockOrSpyAbstract() {
//重载的spy方法
SomeAbstract spy = spy(SomeAbstract.class);
//设置生成器
OtherAbstract mock = mock(OtherAbstract.class, withSettings()
.useConstructor().defaultAnswer(CALLS_REAL_METHODS));
//mock一个非静态内部类
OuterAbstract.InnerAbstract innerMock = mock(OuterAbstract.InnerAbstract.class, withSettings() .useConstructor().outerInstance(outerAbstract).defaultAnswer(CALLS_REAL_METHODS));
}
private abstract static class SomeAbstract {
public SomeAbstract() {
}
}
private abstract static class OtherAbstract {
public OtherAbstract() {
}
}
private static class OuterAbstract {
private abstract class InnerAbstract {
public InnerAbstract() {
}
}
}
委托调用真实实例
- 使用场景
- 带有 interface 的 final 类
- 已经自定义代理的对象
- 带有 finalize 方法的特殊对象,就是避免重复执行
- 和spy的区别
- 标准的 spy包含被 spy 实例的所有状态信息,方法在 spy 对象上被调用。被 spy 的对象只在 mock 创建时被用来拷贝状态信息。如果你通过标准 spy 调用一个方法,这个 spy 会调用其内部的其他方法记录这次操作, 以便后面验证使用。等效于存根 (stubbed)操作。
- mock delegates 只是简单的把所有方法委托给 delegate。delegate 一直被当成它代理的方法使用。如果你 从一个 mock 调用它被委托的方法,它会调用其内部方法,这些调用不会被记录,stubbing 在这里也不会生效。 Mock 的 delegates 相对于标准的 spy 来说功能弱了很多,不过在标准 spy 不能被创建的时候很有用。
/**
* 委托调用真实实例
*/
@Test
void testDelegatesTo() {
CustomMockedList awesomeList = new CustomMockedList();
List<String> mock = mock(List.class, delegatesTo(awesomeList));
doReturn("String").when(mock).get(0);
assertEquals("String", mock.get(0));
verify(mock).get(0);
assertEquals("null", awesomeList.get(0));
}
static final class CustomMockedList extends AbstractList<String> {
public String get(int index) {
return "null";
}
public int size() {
return 0;
}
}
打桩Stub
打桩基本概念与简单案例
- 通过Mockito提供的API模拟方法的返回值的过程称作打桩,以方便测试
- 调用mock对象未打桩的方法,若返回值为基本类型,返回默认值,若为class类返回null
/**
* 打桩
*/
@Test
void testStub() {
// 可以 mock 具体的类型,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);
//打桩
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
//验证打桩结果
assertEquals("first", mockedList.get(0));
assertThrows(RuntimeException.class, () -> mockedList.get(1));
//get(100)未打桩,返回null
assertNull(mockedList.get(100));
verify(mockedList).get(0);
}
给void方法打桩:更强大的打桩方式
doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()- 同时支持给有返回值和无返回的方法打桩
- 避免Spy监控对象调用真实方法带来的混乱(参见:spy对象打桩注意事项)
- 推荐方式
/**
* 给返回值为void的方法打桩,调用时抛出异常
*/
@Test
void testVoidMethod() {
LinkedList mockedList = mock(LinkedList.class);
doThrow(new RuntimeException()).when(mockedList).clear();
assertThrows(RuntimeException.class, mockedList::clear);
}
连续调用打桩
- 多次使用相同参数调用同一个mock对象相同的方法,返回不同的返回值
/**
* 连续调用打桩
*/
@Test
void stubConsecutiveCall() {
TmpMock mock = mock(TmpMock.class);
//连续调用mock方式一
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
// 第一次调用 : 抛出运行时异常
assertThrows(RuntimeException.class, () -> mock.someMethod("some arg"));
// 第二次调用 : 输出 "foo"
assertEquals("foo", mock.someMethod("some arg"));
// 后续调用 : 也是输出 "foo"
assertEquals("foo", mock.someMethod("some arg"));
//连续调用mock方式二
// 第一次调用时返回 "one",第二次返回 "two",第三次返回 "three"
when(mock.someMethod("some arg"))
.thenReturn("one", "two", "three");
assertEquals("one", mock.someMethod("some arg"));
assertEquals("two", mock.someMethod("some arg"));
assertEquals("three", mock.someMethod("some arg"));
//doReturn方法实现
doReturn("haha").doThrow(RuntimeException.class).when(mockedList).get(0);
assertEquals("haha", mockedList.get(0));
assertThrows(RuntimeException.class, () -> mockedList.get(0));
}
使用Answer接口回调
thenReturn()thenThrow()功能足够,Answer接口方式仅供学习
/**
* 使用Answer接口回调打桩
*/
@Test
void stubWithCallBack() {
TmpMock mock = mock(TmpMock.class);
//匿名内部类
when(mock.someMethod(anyString())).thenAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + args[0];
}
});
//lambda表达式
when(mock.someMethod(anyString())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
Object mock1 = invocation.getMock();
return "called with arguments: " + args[0];
});
// 输出 : "called with arguments: foo"
assertEquals("called with arguments: foo", mock.someMethod("foo"));
}
修改未打桩调用的默认返回值
- 一般用不到,复杂的遗留系统可能需要
- 实现
Answer<T>接口,调用mock方法时传入实现类实例
/**
* 修改未打桩调用的默认返回值
*/
@Test
void modifyUnStubDefaultReturnValue() {
List mock = mock(List.class, Mockito.RETURNS_SMART_NULLS);
List mockTwo = mock(List.class, new YourOwnAnswer());
assertEquals(0, mock.size());
assertNull(mock.get(0));
assertEquals(0, mockTwo.size());
assertEquals("default value", mockTwo.get(0));
}
private static class YourOwnAnswer implements Answer<Object> {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
Method method = invocationOnMock.getMethod();
Class<?> returnType = method.getReturnType();
Object res = null;
switch (returnType.getName()) {
case "java.lang.Object":
res = "default value";
break;
default:
res = 0;
break;
}
return res;
}
}
其它注解: @InjectMocks
@InjectMocks:自动将被mock或被spy的字段注入到被该注解修饰的对象中
@Mock
private Person person;
@InjectMocks
private Villa villa;
@Test
@ExtendWith(MockitoExtension.class)
void newAnnotation() {
//验证@InjectMock注入效果
when(person.getName()).thenReturn("Stark");
assertEquals("Stark", villa.ownerName());
}
单行打桩
- 一行代码同时实现打桩与生成mock对象
/**
* 单行打桩
*/
@Test
void inLineStub() {
Person mock = when(mock(Person.class).getName())
.thenReturn("ABC").getMock();
assertEquals("ABC", mock.getName());
}
深度打桩
- 如下示例,对于对象类嵌套对象,如果需要链式调用时,普通方式只能mock嵌套链上全部对象并对相应的方法打桩
- mockito提供专门简化这种嵌套链式调用的打桩方式
- 底层其实是修改默认返回值的实现方式,给链式调用中的方法提供了默认返回以支持链式打桩
/**
* 深度打桩
*/
@Test
void deepStub() {
//普通方式给链式调用打桩
City city = when(mock(City.class).getName()).thenReturn("Door").getMock();
Address address = when(mock(Address.class).getCity()).thenReturn(city).getMock();
User user = when(mock(User.class).getAddress()).thenReturn(address).getMock();
assertEquals("Door", user.getAddress().getCity().getName());
verify(city).getName();
verify(address).getCity();
verify(user).getAddress();
// 使用RETURNS_DEEP_STUBS为链式调用打桩
User mockUser = mock(User.class, RETURNS_DEEP_STUBS);
when(mockUser.getAddress().getCity().getName()).thenReturn("Garden");
// 现在,当你调用mockUser.getAddress().getCity().getName()时,应该返回"mockCity"
assertEquals("Garden", mockUser.getAddress().getCity().getName());
}
class User {
private Address address;
// constructor, getters and setters...
public Address getAddress() {
return address;
}
}
class Address {
private City city;
// constructor, getters and setters...
public City getCity() {
return city;
}
}
class City {
private String name;
// constructor, getters and setters...
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
参数
参数匹配器
- 方便对同一类型参数或符合自定义逻辑的参数打桩,避免重复编写打桩代码
ArgumentMatcher<T>接口为参数匹配器接口,实现该接口并配合Mockito.argThat()方法可自定义参数匹配器- Mockito提供了大量内置参数匹配器及相应的工具方法
/**
* 参数匹配:验证调用时传入的参数是否符合要求
*/
@Test
void testArgumentMatcher() {
LinkedList mockedList = mock(LinkedList.class);
// 使用内置的 anyInt() 参数匹配器
when(mockedList.get(anyInt())).thenReturn("element");
// 使用自定义的参数匹配器( 在isValid() 函数中返回你自己的匹配器实现 )
when(mockedList.contains(argThat(new ArgumentMatcher<Object>() {
@Override
public boolean matches(Object o) {
return o instanceof Integer;
}
}))).thenReturn(false);
assertEquals("element",mockedList.get(2342432));
assertTrue(mockedList.contains(234324));
//参数匹配Integer,实际输入String,未mock方法返回默认值false(若返回值非基本类型,返回null)
assertFalse(mockedList.contains("1.1"));
}
参数捕获
- 方便在验证时捕获交互时的实际参数用于后续处理(assert验证等)
- 实现方式
- 编程实现
@Captor注解实现
//注解实现
@Captor
private ArgumentCaptor<Person> argument;
/**
* 参数捕获
*/
@Test
void captureArguments() {
List spy = spy(List.class);
//编程实现
//由于注解是成员变量,此处为局部变量,同时使用并不会报错,但不建议
// ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
//调用方法
spy.remove(new Person("John"));
// 参数捕获
verify(spy).remove(argument.capture());
// 使用equal断言
assertEquals("John", argument.getValue().getName());
}
文档信息
- 本文作者:Ling He
- 本文链接:https://GoggleHe.github.io/2023/10/20/Mockito/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)