1. 概述

Mockito 是一个广泛使用的 Java 应用程序单元测试框架。它提供了各种API来模拟对象的行为。在这个教程中,我们将探讨如何使用 doAnswer()thenReturn() 的桩插件技术,并进行比较。这两种API都可以用于方法的桩插件或模拟,但在某些情况下,我们只能选择其中一个。

2. 依赖项

我们的代码将使用 Mockito 与 JUnit 5 进行示例代码演示,需要在 pom.xml 文件中添加一些依赖:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.11.0</version>
    <scope>test</scope>
</dependency>

您可以在 Maven 中央仓库JUnit 5 引擎库Mockito 库 找到相关的API库。

3. 使用 thenReturn() 桩插件方法

在 Mockito 中,我们可以使用 thenReturn() 桩插件技术来模拟返回值的方法。 为了演示,我们将使用 thenReturn()doAnswer() 来测试列表的 get()add() 操作:

public class BaeldungList extends AbstractList<String> {
    @Override
    public String get(final int index) {
        return null;
    }

    @Override
    public void add(int index, String element) {
        // no-op
    }

    @Override
    public int size() {
        return 0;
    }
}

在上面的示例代码中,get() 方法返回一个 String。首先,我们将使用 thenReturn() 桩插件 get() 方法,并通过断言其返回值与桩插件方法相同来验证调用:

@Test
void givenThenReturn_whenGetCalled_thenValue() {
    BaeldungList myList = mock(BaeldungList.class);

    when(myList.get(anyInt()))
      .thenReturn("answer me");

    assertEquals("answer me", myList.get(1));
}

3.1. 使用 thenReturn() 返回多个值

此外,thenReturn() API 允许在连续调用中返回不同的值。我们可以通过链式调用来返回多个值。而且,我们可以在单个方法调用中传递多个值:

@Test
void givenThenReturn_whenGetCalled_thenReturnChaining() {
    BaeldungList myList = mock(BaeldungList.class);

    when(myList.get(anyInt()))
      .thenReturn("answer one")
      .thenReturn("answer two");

    assertEquals("answer one", myList.get(1));
    assertEquals("answer two", myList.get(1));
}

@Test
void givenThenReturn_whenGetCalled_thenMultipleValues() {
    BaeldungList myList = mock(BaeldungList.class);

    when(myList.get(anyInt()))
      .thenReturn("answer one", "answer two");

    assertEquals("answer one", myList.get(1));
    assertEquals("answer two", myList.get(1));
}

4. 使用 doAnswer() 桩插件 void 方法

add() 方法是一个不返回任何值的 void 方法。由于 thenReturn() 桩插件无法用于 void 方法,我们不能使用它来桩插件 add() 方法。相反,我们将使用 doAnswer(),因为它允许桩插件 void 方法。 所以,我们将使用 doAnswer() 来桩插件 add() 方法,当调用 add() 方法时,提供的 Answer 将被调用:

@Test
void givenDoAnswer_whenAddCalled_thenAnswered() {
    BaeldungList myList = mock(BaeldungList.class);

    doAnswer(invocation -> {
        Object index = invocation.getArgument(0);
        Object element = invocation.getArgument(1);

        // verify the invocation is called with the correct index and element
        assertEquals(3, index);
        assertEquals("answer", element);

        // return null as this is a void method
        return null;
    }).when(myList)
      .add(any(Integer.class), any(String.class));
    myList.add(3, "answer");
}

doAnswer() 中,我们验证 add() 方法的调用,并断言它被调用时的参数符合预期。

4.1. 使用 doAnswer() 桩插件非 void 方法

由于我们可以使用返回值而非 nullAnswer 来桩插件非 void 方法,因此可以使用 doAnswer() 方法来桩插件此类方法。例如,我们将测试 get() 方法,通过 doAnswer() 桩插件并返回一个返回 StringAnswer

@Test
void givenDoAnswer_whenGetCalled_thenAnswered() {
    BaeldungList myList = mock(BaeldungList.class);

    doAnswer(invocation -> {
        Object index = invocation.getArgument(0);

        // verify the invocation is called with the index
        assertEquals(1, index);

        // return the value we want 
        return "answer me";
    }).when(myList)
      .get(any(Integer.class));

    assertEquals("answer me", myList.get(1));
}

4.2. 使用 doAnswer() 返回多个值

需要注意的是,我们只能在 doAnswer() 方法中返回一个 Answer。然而,我们可以在 doAnswer() 方法中放置条件逻辑,根据调用的参数返回不同的值。因此,在下面的示例代码中,我们将根据调用 get() 方法时的索引返回不同的值:

@Test
void givenDoAnswer_whenGetCalled_thenAnsweredConditionally() {
    BaeldungList myList = mock(BaeldungList.class);

    doAnswer(invocation -> {
        Integer index = invocation.getArgument(0);
        switch (index) {
            case 1: 
                return "answer one";
            case 2: 
                return "answer two";
            default: 
                return "answer " + index;
        }
    }).when(myList)
      .get(anyInt());

    assertEquals("answer one", myList.get(1));
    assertEquals("answer two", myList.get(2));
    assertEquals("answer 3", myList.get(3));
}

5. 总结

Mockito 框架提供了多种桩插件/模拟技术,如 doAnswer()doReturn()thenReturn()thenAnswer() 等,以方便处理各种类型的 Java 代码及其测试。我们使用了 doAnswer()thenReturn() 来桩插件非 void 方法,并进行了类似的测试。然而,我们只能使用 doAnswer() 来桩插件 void 方法,因为 thenReturn() 方法无法执行此功能。 如往常一样,所有代码示例可在 GitHub 上找到。