1. 简介

在大多数情况下,Mockito 提供的默认 mock 配置已经足够使用。

但有时我们可能需要在创建 mock 对象时提供额外的配置选项。这在调试、处理遗留代码或覆盖某些边界场景时尤其有用。

之前我们已经了解了如何使用 lenient mocks(宽松 mock)。在这篇文章中,我们将继续学习 MockSettings 接口提供的其他实用功能。

2. Mock 配置详解

简单来说,MockSettings 接口提供了一套 Fluent API,让我们可以在创建 mock 对象时轻松添加和组合各种配置。

当我们创建一个 mock 对象时,它其实已经携带了一组默认配置。例如:

List mockedList = mock(List.class);

在内部,Mockito 的 mock 方法会委托给另一个重载方法,并传入一组默认配置:

public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings());
}

我们来看看这些默认配置:

public static MockSettings withSettings() {
    return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
}

可以看到,Mockito 默认只配置了 mock 对象的默认返回行为。通常,RETURNS_DEFAULTS 会返回一些“空值”,比如 null、0、空集合等。

关键点在于:如果需要,我们可以自定义这些配置项

接下来的章节中,我们会通过一些实际例子来说明这些配置的实际用途。

3. 自定义默认返回值

了解了 mock 配置的基本原理后,我们来看看如何自定义 mock 对象的默认返回值。

假设我们有如下代码:

PizzaService service = mock(PizzaService.class);
Pizza pizza = service.orderHouseSpecial();
PizzaSize size = pizza.getSize();

运行这段代码时,不出意外会抛出 NullPointerException,因为 orderHouseSpecial() 方法没有被 stub,返回了 null

这在大多数情况下没问题,但在处理遗留代码时,如果 mock 对象层级较深,排查这类异常可能会很麻烦。

为了解决这个问题,我们可以通过 mock 配置来指定一个不同的默认返回行为:

PizzaService pizzaService = mock(PizzaService.class, withSettings().defaultAnswer(RETURNS_SMART_NULLS));

使用 RETURNS_SMART_NULLS 时,Mockito 会提供更有意义的错误提示,明确指出是哪个方法调用导致了问题:

org.mockito.exceptions.verification.SmartNullPointerException: 
You have a NullPointerException here:
-> at com.baeldung.mockito.mocksettings.MockSettingsUnitTest.whenServiceMockedWithSmartNulls_thenExceptionHasExtraInfo(MockSettingsUnitTest.java:45)
because this method call was *not* stubbed correctly:
-> at com.baeldung.mockito.mocksettings.MockSettingsUnitTest.whenServiceMockedWithSmartNulls_thenExceptionHasExtraInfo(MockSettingsUnitTest.java:44)
pizzaService.orderHouseSpecial();

这在调试测试代码时能节省不少时间。

Answers 枚举还提供了其他几个常用的默认行为:

  • RETURNS_DEEP_STUBS:返回“深层 stub”,✅ 特别适合用于 Fluent API
  • RETURNS_MOCKS:返回普通默认值(如空集合、空字符串),之后尝试返回 mock 对象
  • CALLS_REAL_METHODS:未 stub 的方法会调用真实实现

4. 命名 Mock 与详细日志

我们可以通过 MockSettingsname() 方法给 mock 对象起个名字,这对调试非常有帮助,因为该名称会出现在所有验证错误信息中:

PizzaService service = mock(PizzaService.class, withSettings()
  .name("pizzaServiceMock")
  .verboseLogging()
  .defaultAnswer(RETURNS_SMART_NULLS));

在这个例子中,我们还通过 verboseLogging() 启用了详细日志功能。

⚠️ 启用后,所有在该 mock 上的方法调用都会实时输出到标准输出流中,非常适合在调试测试时排查 mock 交互问题。

运行测试时,控制台会输出类似如下信息:

pizzaServiceMock.orderHouseSpecial();
   invoked: -> at com.baeldung.mockito.mocksettings.MockSettingsUnitTest.whenServiceMockedWithNameAndVerboseLogging_thenLogsMethodInvocations(MockSettingsUnitTest.java:36)
   has returned: "Mock for Pizza, hashCode: 366803687" (com.baeldung.mockito.fluentapi.Pizza$MockitoMock$168951489)

💡 注意:如果使用 @Mock 注解创建 mock,mock 的名称会自动使用字段名。

5. 实现额外接口

有时候我们可能需要让 mock 对象实现额外的接口。这在处理无法重构的遗留代码时特别有用。

假设我们有这样一个接口:

public interface SpecialInterface {
    // Public methods
}

以及一个类:

public class SimpleService {

    public SimpleService(SpecialInterface special) {
        Runnable runnable = (Runnable) special;
        runnable.run();
    }
    // More service methods
}

虽然这段代码不符合 clean code 原则,但如果我们必须为其编写单元测试,就会遇到问题:

SpecialInterface specialMock = mock(SpecialInterface.class);
SimpleService service = new SimpleService(specialMock);

运行时会抛出 ClassCastException。为了解决这个问题,我们可以使用 extraInterfaces() 方法让 mock 实现多个接口:

SpecialInterface specialMock = mock(SpecialInterface.class, withSettings()
  .extraInterfaces(Runnable.class));

现在 mock 创建就不会失败了。不过还是那句话,❌ 强制类型转换未声明的接口不是好习惯,尽量避免

6. 传递构造函数参数

最后一个例子中,我们来看看如何通过 MockSettings 来调用真实的构造函数并传入参数:

@Test
public void whenMockSetupWithConstructor_thenConstructorIsInvoked() {
    AbstractCoffee coffeeSpy = mock(AbstractCoffee.class, withSettings()
      .useConstructor("espresso")
      .defaultAnswer(CALLS_REAL_METHODS));

    assertEquals("Coffee name: ", "espresso", coffeeSpy.getName());
}

在这个例子中,Mockito 会尝试使用 String 类型的构造函数来创建 AbstractCoffee 的 mock 实例,并将默认行为设置为调用真实方法。

这在我们需要测试构造函数中的逻辑,或者让被测类进入特定状态时非常有用。同时,这也适用于 spy 抽象类的场景。

7. 总结

在这篇文章中,我们学习了如何通过 MockSettings 来创建更灵活的 mock 对象。

⚠️ 不过需要再次强调:虽然这些功能非常实用,有时甚至是不可避免的,但我们仍应尽量保持测试代码的简洁性,避免过度配置。

源码可以在 GitHub 上找到。


原始标题:Overview of Mockito MockSettings