1. Overview
1.概述
More often than not, the default settings provided by Mockito for our mock objects should be more than sufficient.
更多时候,Mockito为我们的模拟对象提供的默认设置应该是足够的。
However, there may be occasions when we need to provide additional mock settings during mock creation. This might be useful when debugging, dealing with legacy code, or covering some corner cases.
然而,在某些情况下,我们可能需要在创建模拟时提供额外的模拟设置。这在调试、处理遗留代码或覆盖一些角落情况时可能很有用。
In a previous tutorial, we learned how to work with lenient mocks. In this quick tutorial, we’ll learn how to use some other useful features the MockSettings interface provides.
在之前的教程中,我们学习了如何使用lenient mocks。在这个快速教程中,我们将学习如何使用MockSettings接口提供的一些其他有用的功能。
2. Mock Settings
2.模拟设置
Put simply, the MockSettings interface provides a Fluent API that allows us to easily add and combine additional mock settings during mock creation.
简单地说,MockSettings接口提供了一个Fluent API,允许我们在创建模拟时轻松添加和组合其他模拟设置。
When we create a mock object, all our mocks carry a set of default settings. Let’s take a look at a simple mock example:
当我们创建一个模拟对象时,我们所有的模拟对象都带有一组默认设置。让我们看一下一个简单的模拟例子。
List mockedList = mock(List.class);
Behind the scenes the Mockito mock method delegates to another overloaded method with a set of default settings for our mock:
在幕后,Mockito的mock方法委托给另一个重载方法,为我们的mock提供了一套默认设置。
public static <T> T mock(Class<T> classToMock) {
return mock(classToMock, withSettings());
}
Let’s have a look at our default settings:
让我们看一下我们的默认设置。
public static MockSettings withSettings() {
return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
}
As we can see, our standard set of settings for our mock objects is very simple. We configure the default answer for our mock interactions. Typically, using RETURNS_DEFAULTS will return some empty value.
正如我们所看到的,我们的模拟对象的标准设置非常简单。我们为我们的模拟交互配置了默认的答案。通常,使用RETURNS_DEFAULTS会返回一些空值。
The important point to take away from this is that we can provide our own set of custom settings to our mock objects if the need arises.
从中可以看出的重要一点是,如果有需要,我们可以为我们的模拟对象提供我们自己的一套自定义设置。
In the next sections, we’ll see some examples of when this might come in handy.
在接下来的章节中,我们将看到一些例子,说明这可能会派上用场。
3. Providing a Different Default Answer
3.提供一个不同的默认答案
Now that we understand a bit more about mock settings, let’s see seeing how we can change the default return value for a mock object.
现在我们对模拟设置有了更多的了解,让我们看看如何能改变模拟对象的默认返回值。
Let’s imagine we have a very simple setup for a mock:
让我们想象一下,我们有一个非常简单的模拟设置。
PizzaService service = mock(PizzaService.class);
Pizza pizza = service.orderHouseSpecial();
PizzaSize size = pizza.getSize();
When we run this code as expected, we’ll get a NullPointerException because our unstubbed method orderHouseSpecial returns null.
当我们按照预期运行这段代码时,我们会得到一个NullPointerException,因为我们未存的方法orderHouseSpecial返回null。
This is OK, but sometimes when working with legacy code, we might need to handle a complicated hierarchy of mock objects, and it can be time-consuming to locate where these types of exceptions occur.
这是可以的,但有时在处理遗留代码时,我们可能需要处理复杂的模拟对象的层次结构,要找到这些类型的异常发生的地方可能很费时间。
To help us combat this, we can provide a different default answer via our mock settings during mock creation:
为了帮助我们解决这个问题,我们可以在创建模拟时通过模拟设置提供一个不同的默认答案。
PizzaService pizzaService = mock(PizzaService.class, withSettings().defaultAnswer(RETURNS_SMART_NULLS));
By using the RETURNS_SMART_NULLS as our default answer, Mockito gives us a much more meaningful error message that shows us exactly where the incorrect stubbing occurred:
通过使用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();
This can really save us some time when debugging our test code. The Answers enumeration also supplies some other preconfigured mock answers of note:
在调试我们的测试代码时,这真的可以为我们节省一些时间。Answers枚举还提供了一些其他值得注意的预配置模拟答案。
- RETURNS_DEEP_STUBS – an answer that returns deep stubs – this can be useful when working with Fluent APIs
- RETURNS_MOCKS – using this answer will return ordinary values such as empty collections or empty strings, and thereafter, it tries to return mocks
- CALLS_REAL_METHODS – as the name suggests, when we use this implementation, unstubbed methods will delegate to the real implementation
4. Naming Mocks and Verbose Logging
4.命名Mock和Verbose Logging
We can give our mock a name by using the name method of MockSettings. This can be particularly useful for debugging as the name we provide is used in all verification errors:
我们可以通过使用MockSettings的name方法给我们的mock一个名字。这对调试特别有用,因为我们提供的名字会在所有验证错误中使用。
PizzaService service = mock(PizzaService.class, withSettings()
.name("pizzaServiceMock")
.verboseLogging()
.defaultAnswer(RETURNS_SMART_NULLS));
In this example, we combine this naming feature with verbose logging by using the method verboseLogging().
在这个例子中,我们通过使用verboseLogging()方法,将这个命名功能和粗略的日志记录结合起来。
Using this method enables real-time logging to the standard output stream for method invocations on this mock. Likewise, it can be used during test debugging in order to find wrong interactions with a mock.
使用该方法可以实时记录该模拟的方法调用到标准输出流中。同样,它也可以在测试调试中使用,以便发现与模拟的错误交互。
When we run our test, we’ll see some output on the console:
当我们运行我们的测试时,我们会在控制台看到一些输出。
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)
It’s interesting to note that if we’re using the @Mock annotation, our mocks automatically take the field name as the mock name.
值得注意的是,如果我们使用@Mock注解,我们的模拟会自动将字段名作为模拟的名称。
5. Mocking Extra Interfaces
5.嘲弄额外的接口
Occasionally, we might want to specify extra interfaces our mock should implement. Again, this might be useful when working with legacy code that we cannot refactor.
偶尔,我们可能想指定我们的mock应该实现的额外接口。同样,当我们在处理无法重构的遗留代码时,这可能很有用。
Let’s imagine we have a special interface:
让我们想象一下,我们有一个特殊的接口。
public interface SpecialInterface {
// Public methods
}
And a class that uses this interface:
还有一个使用这个接口的类。
public class SimpleService {
public SimpleService(SpecialInterface special) {
Runnable runnable = (Runnable) special;
runnable.run();
}
// More service methods
}
Of course, this is not clean code, but if we’re forced to write a unit test for this, we’ll more than likely have problems:
当然,这不是干净的代码,但如果我们被迫为此写一个单元测试,我们更可能会出现问题。
SpecialInterface specialMock = mock(SpecialInterface.class);
SimpleService service = new SimpleService(specialMock);
When we run this code, we’ll get a ClassCastException. In order to rectify this, we can create our mock with multiple types using the extraInterfaces method:
当我们运行这段代码时,我们会得到一个ClassCastException。为了纠正这个问题,我们可以使用extraInterfaces方法创建具有多种类型的mock。
SpecialInterface specialMock = mock(SpecialInterface.class, withSettings()
.extraInterfaces(Runnable.class));
Now, our mock creation code won’t fail, but we should really emphasize that casting to an undeclared type is not cool.
现在,我们的模拟创建代码不会失败,但我们真的应该强调,向一个未声明的类型投递是不酷的。
6. Supplying Constructor Arguments
6.提供构造函数参数
In this last example, we’ll see how we can use MockSettings to call a real constructor with an argument value:
在这最后一个例子中,我们将看到如何使用MockSettings来调用一个带有参数值的真实构造函数。
@Test
public void whenMockSetupWithConstructor_thenConstructorIsInvoked() {
AbstractCoffee coffeeSpy = mock(AbstractCoffee.class, withSettings()
.useConstructor("espresso")
.defaultAnswer(CALLS_REAL_METHODS));
assertEquals("Coffee name: ", "espresso", coffeeSpy.getName());
}
This time around, Mockito attempts to use the constructor with a String value when creating the instance of our AbstractCoffee mock. We also configure the default answer to delegate to the real implementation.
这一次,在创建我们的AbstractCoffee模拟实例时,Mockito试图使用带有String值的构造函数。我们还配置了默认的答案来委托给真正的实现。
This might be useful if we have some logic inside our constructor that we want to test or trigger to leave our class under test in some certain state. It’s also useful when spying on abstract classes.
如果我们的构造函数内有一些我们想要测试的逻辑,或者触发让我们的被测类处于某种状态,这可能很有用。在刺探抽象类时,它也很有用。
7. Conclusion
7.结语
In this quick tutorial, we’ve seen how we can create our mocks with additional mock settings.
在这个快速教程中,我们已经看到了如何用额外的模拟设置来创建我们的模拟。
However, we should reiterate that although this is sometimes useful and probably unavoidable, we should strive in most cases to write simple tests using simple mocks.
然而,我们应该重申,尽管这有时是有用的,而且可能是不可避免的,但在大多数情况下,我们应该努力使用简单的模拟来编写简单的测试。
As always, the full source code of the article is available over on GitHub.
一如既往,该文章的完整源代码可在GitHub上获得。