1.Overview
More often than not, the default settings provided by Mockito for our mock objects should be more than sufficient.
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.
2. Mock Settings
Put simply, the MockSettings interface provides a Fluent API that allows us to easily add and combine additional mock settings during mock creation.
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:
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.
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
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.
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:
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:
- 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
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:
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().
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.
5. Mocking Extra Interfaces
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.
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:
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
In this last example, we'll see how we can use MockSettings to call a real constructor with an argument value:
@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.
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
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.
The post Overview of Mockito MockSettings first appeared on Baeldung.