1. 概述
在本教程中,我们将深入探讨如何在 Mockito 中高效地使用 Spy。
我们会介绍 @Spy
注解的使用方式、如何对 Spy 进行 stub(打桩)操作,以及 Mock 和 Spy 之间的本质区别。
当然,如果你还想了解更多 Mockito 的内容,可以查看我们的 Mockito 系列文章。
2. 简单的 Spy 示例
我们从一个简单的例子开始,说明 如何使用 Spy。
简单来说,Mockito 提供了 Mockito.spy()
方法来 对真实对象进行监控(spy on a real object)。
这允许我们调用该对象的所有正常方法,同时还能像 mock 一样跟踪所有交互行为。
下面是一个快速示例,我们对一个已有的 ArrayList
对象进行 spy:
@Test
void givenUsingSpyMethod_whenSpyingOnList_thenCorrect() {
List<String> list = new ArrayList<String>();
List<String> spyList = spy(list);
spyList.add("one");
spyList.add("two");
verify(spyList).add("one");
verify(spyList).add("two");
assertThat(spyList).hasSize(2);
}
注意:**add()
方法确实被调用了**,并且 spyList
的 size 确实变成了 2。
3. 使用 @Spy 注解
接下来我们看看如何使用 @Spy
注解。你可以用它来替代 spy()
方法:
@Spy
List<String> spyList = new ArrayList<String>();
@Test
void givenUsingSpyAnnotation_whenSpyingOnList_thenCorrect() {
spyList.add("one");
spyList.add("two");
verify(spyList).add("one");
verify(spyList).add("two");
assertThat(spyList).hasSize(2);
}
要 启用 Mockito 的注解功能(如 @Spy
、@Mock
等),你需要使用 @ExtendWith(MockitoExtension.class)
来初始化 mock 并处理严格 stub 行为。
4. 对 Spy 进行 Stub 操作
现在我们来看看如何对 Spy 进行 stub 操作。我们可以使用和 mock 相同的语法来配置或覆盖方法的行为。
在这个例子中,我们使用 doReturn()
来覆盖 size()
方法:
@Test
void givenASpy_whenStubbingTheBehaviour_thenCorrect() {
List<String> list = new ArrayList<String>();
List<String> spyList = spy(list);
assertEquals(0, spyList.size());
doReturn(100).when(spyList).size();
assertThat(spyList).hasSize(100);
}
✅ 通过这种方式,即使底层 list 是空的,我们也能让它返回我们设定的值。
5. Mock 与 Spy 的区别
我们来聊聊 Mockito 中 Mock 和 Spy 的区别。这里我们不谈理论层面的不同,只关注在 Mockito 中它们的行为差异。
当你使用 Mockito 创建一个 mock 时,它是基于某个类型的 Class 创建的,而不是基于一个已有的实例。mock 只是创建了一个该类的“空壳”实例,用于跟踪所有交互行为。
而 Spy 则是包装了一个已有的实例。它的行为和普通实例一样,唯一的不同是它也会被监控,以便跟踪所有交互。
下面的例子展示了如何创建一个 ArrayList
类的 mock:
@Test
void whenCreateMock_thenCreated() {
List mockedList = mock(ArrayList.class);
mockedList.add("one");
verify(mockedList).add("one");
assertThat(mockedList).hasSize(0);
}
可以看到,向 mock list 中添加元素并没有真正添加任何东西;只是调用了方法,没有副作用。
而 spy 则不同,它会真正调用 add
方法,并把元素添加到底层 list 中:
@Test
void whenCreateSpy_thenCreate() {
List spyList = Mockito.spy(new ArrayList());
spyList.add("one");
Mockito.verify(spyList).add("one");
assertThat(spyList).hasSize(1);
}
✅ 所以,如果你需要调用真实方法,但又想监控调用过程,Spy 是更好的选择。
6. 理解 Mockito 的 NotAMockException
在最后一节中,我们来谈谈 Mockito 中常见的异常之一:NotAMockException。这个异常通常在你错误地使用 mock 或 spy 时出现。
我们来看一个会触发该异常的例子:
List<String> list = new ArrayList<String>();
doReturn(100).when(list).size();
运行这段代码时,会抛出如下异常:
org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
Example of correct stubbing:
doThrow(new RuntimeException()).when(mock).someMethod();
❌ 错误信息非常明确:传给 when()
的参数不是一个 mock!
在上面的例子中,list
是一个普通对象,并不是 mock。Mockito 的 when()
方法期望传入的是一个 mock 或 spy 对象。
好消息是,异常信息甚至告诉你正确的调用方式应该是什么样的。根据提示,我们来修正一下代码:
final List<String> spyList = spy(new ArrayList<>());
assertThatNoException().isThrownBy(() -> doReturn(100).when(spyList).size());
✅ 现在代码运行正常,不再抛出 NotAMockException
。
7. 总结
在这篇文章中,我们介绍了 Mockito 中 Spy 的常见用法:
- 如何创建 Spy
- 使用
@Spy
注解 - 如何对 Spy 进行 stub 操作
- Mock 与 Spy 的关键区别
这些示例的完整代码可以在 GitHub 上找到。
这是一个 Maven 项目,导入后即可直接运行。
最后,如果你还想了解更多 Mockito 的内容,可以继续浏览我们的 Mockito 系列文章。