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 系列文章


原始标题:Mockito - Using Spies | Baeldung

« 上一篇: Baeldung周报44
» 下一篇: Spring Profiles 详解