1.概述
通常,Mockito为我们的模拟对象提供的默认设置应该绰绰有余。
但是,有时可能需要在模拟创建期间提供其他模拟设置。这在调试,处理遗留代码或涵盖一些极端情况时可能很有用。
在先前的教程中,我们学习了如何使用宽大的模拟。在本快速教程中,我们将学习如何使用MockSettings
界面提供的其他一些有用功能。
2.模拟设置
简而言之, MockSettings
接口提供了Fluent API,使我们能够在模拟创建过程中轻松添加和组合其他模拟设置。
创建模拟对象时,所有模拟都带有一组默认设置。让我们看一个简单的模拟示例:
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);
}
如我们所见,模拟对象的标准设置非常简单。我们为模拟互动配置默认答案。通常,使用RETURNS_DEFAULTS
将返回一些空值。
摆脱这一点的重要一点是,如果需要,我们可以为模拟对象提供一组自定义设置。
在接下来的部分中,我们将看到一些方便使用的示例。
3.**提供不同的默认答案**
现在,我们对模拟设置有了更多的了解,让我们看看如何更改模拟对象的默认返回值。
假设我们有一个非常简单的模拟设置:
PizzaService service = mock(PizzaService.class);
Pizza pizza = service.orderHouseSpecial();
PizzaSize size = pizza.getSize();
当我们按预期运行此代码时,将得到NullPointerException
因为未打桩的方法orderHouseSpecial
返回null
。
可以,但是有时在使用旧版代码时,我们可能需要处理复杂的模拟对象层次结构,并且定位这些类型的异常发生的位置可能很耗时。
为了帮助我们解决这个问题,我们可以在模拟创建过程中通过模拟设置提供其他默认答案:
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
–返回深层存根的答案–在使用Fluent API时可能很有用 -
RETURNS_MOCKS
–使用此答案将返回普通值,例如空集合或空字符串,然后,它尝试返回模拟 -
CALLS_REAL_METHODS
–顾名思义,当我们使用此实现时,未打桩的方法将委托给实际的实现
4.命名模拟和详细记录
我们可以通过使用MockSettings
的name
方法为模拟对象命名。这对于调试特别有用,因为我们提供的名称用于所有验证错误:
PizzaService service = mock(PizzaService.class, withSettings()
.name("pizzaServiceMock")
.verboseLogging()
.defaultAnswer(RETURNS_SMART_NULLS));
在此示例中,我们通过使用verboseLogging()
方法将此命名功能与详细日志记录结合在一起。
使用此方法可以实时记录到标准输出流中,以便对此模拟方法进行方法调用。同样,可以在测试调试期间使用它来查找与模拟的错误交互。
运行测试时,我们将在控制台上看到一些输出:
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
注释,我们的模拟将自动将字段名称作为模拟名称。
5.模拟额外的接口
有时,我们可能想指定模拟应实现的额外接口。同样,当使用无法重构的旧代码时,这可能很有用。
假设我们有一个特殊的接口:
public interface SpecialInterface {
// Public methods
}
和使用该接口的类:
public class SimpleService {
public SimpleService(SpecialInterface special) {
Runnable runnable = (Runnable) special;
runnable.run();
}
// More service methods
}
当然,这不是干净的代码,但是如果我们被迫为此编写单元测试,则很可能会遇到问题:
SpecialInterface specialMock = mock(SpecialInterface.class);
SimpleService service = new SimpleService(specialMock);
运行此代码时,将得到ClassCastException
。为了解决这个问题,我们可以使用extraInterfaces
方法创建具有多种类型的模拟:
SpecialInterface specialMock = mock(SpecialInterface.class, withSettings()
.extraInterfaces(Runnable.class));
现在,我们的模拟创建代码不会失败,但是我们应该真正强调,强制转换为未声明的类型并不是很酷。
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在创建AbstractCoffee
模拟的实例时尝试使用带有String
值的构造函数。我们还配置了默认答案,以委托给实际的实现。
如果我们在构造函数中有一些逻辑要测试或触发,以使我们的类处于某种特定状态,这可能很有用。监视抽像类时,它也很有用。
7.结论
在本快速教程中,我们了解了如何使用其他模拟设置来创建模拟。
但是,我们应该重申,尽管这有时是有用的,并且可能是不可避免的,但在大多数情况下,我们应该努力使用简单的模拟编写简单的测试。
与往常一样,可以在GitHub上获得本文的完整源代码。
0 评论