«1.Обзор
Чаще всего настроек по умолчанию, предоставляемых Mockito для наших фиктивных объектов, должно быть более чем достаточно.
Однако могут быть случаи, когда нам нужно предоставить дополнительные настройки макета во время создания макета. Это может быть полезно при отладке, работе с устаревшим кодом или в некоторых крайних случаях.
В предыдущем уроке мы узнали, как работать с мягкими моками. В этом кратком руководстве мы узнаем, как использовать некоторые другие полезные функции, предоставляемые интерфейсом MockSettings.
2. Параметры макета
Проще говоря, интерфейс MockSettings предоставляет Fluent API, который позволяет нам легко добавлять и комбинировать дополнительные параметры макета во время создания макета.
Когда мы создаем макет объекта, все наши макеты имеют набор настроек по умолчанию. Давайте взглянем на простой пример макета:
List mockedList = mock(List.class);
За кулисами метод макета Mockito делегирует другому перегруженному методу с набором настроек по умолчанию для нашего макета:
public static <T> T mock(Class<T> classToMock) {
return mock(classToMock, withSettings());
}
Давайте посмотрим на наш настройки по умолчанию:
public static MockSettings withSettings() {
return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
}
Как мы видим, наш стандартный набор настроек для наших фиктивных объектов очень прост. Мы настраиваем ответ по умолчанию для наших фиктивных взаимодействий. Как правило, использование RETURN_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. Это может быть особенно полезно для отладки, поскольку имя, которое мы предоставляем, используется во всех ошибках проверки:
В этом примере мы объединяем эту функцию именования с подробным ведением журнала с помощью метода verboseLogging().
PizzaService service = mock(PizzaService.class, withSettings()
.name("pizzaServiceMock")
.verboseLogging()
.defaultAnswer(RETURNS_SMART_NULLS));
Использование этого метода позволяет вести журнал в реальном времени в стандартный поток вывода для вызовов методов на этом макете. Точно так же его можно использовать во время отладки тестов, чтобы найти неправильное взаимодействие с макетом.
Когда мы запустим наш тест, мы увидим некоторый вывод на консоли:
Интересно отметить, что если мы используем аннотацию @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)
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 пытается использовать конструктор со значением String при создании экземпляра нашего макета AbstractCoffee. Мы также настраиваем ответ по умолчанию для делегирования реальной реализации.
Это может быть полезно, если у нас есть некоторая логика внутри нашего конструктора, которую мы хотим протестировать или запустить, чтобы оставить наш тестируемый класс в каком-то определенном состоянии. Это также полезно при слежке за абстрактными классами.
7. Заключение
В этом кратком руководстве мы увидели, как мы можем создавать наши моки с дополнительными настройками.
Тем не менее, мы должны повторить, что, хотя иногда это полезно и, вероятно, неизбежно, в большинстве случаев мы должны стремиться писать простые тесты, используя простые макеты.
Как всегда, полный исходный код статьи доступен на GitHub.