Перейти к основному содержимому

Издевательство над статическими методами с помощью Mockito

· 4 мин. чтения

1. Обзор

При написании тестов мы часто сталкиваемся с ситуацией, когда нам нужно имитировать статический метод. До версии 3.4.0 Mockito было невозможно напрямую имитировать статические методы — только с помощью PowerMockito .

В этом уроке мы рассмотрим, как теперь мы можем имитировать статические методы, используя последнюю версию Mockito.

Чтобы узнать больше о тестировании с помощью Mockito, ознакомьтесь с нашей обширной серией статей о Mockito .

2. Простой статический служебный класс

В центре внимания наших тестов будет простой статический служебный класс:

public class StaticUtils {

private StaticUtils() {}

public static List<Integer> range(int start, int end) {
return IntStream.range(start, end)
.boxed()
.collect(Collectors.toList());
}

public static String name() {
return "ForEach";
}
}

Для демонстрационных целей у нас есть один метод с некоторыми аргументами и другой, который просто возвращает String .

3. Зависимости

Давайте начнем с добавления зависимости mockito-inline в наш pom.xml :

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.8.0</version>
<scope>test</scope>
</dependency>

Стоит отметить, что в какой-то момент, согласно документации , этот функционал вполне может быть интегрирован в более привычную зависимость mockito-core .

4. Несколько слов о тестировании статических методов

Вообще говоря, некоторые могут сказать, что при написании чистого объектно-ориентированного кода нам не нужно имитировать статические классы. Обычно это может намекать на проблему дизайна или запах кода в нашем приложении.

Почему? Во-первых, класс, зависящий от статического метода, имеет тесную связь, а во-вторых, это почти всегда приводит к сложно тестируемому коду. В идеале класс не должен отвечать за получение своих зависимостей, и по возможности их следует внедрить извне.

Таким образом, всегда стоит исследовать, можем ли мы реорганизовать наш код, чтобы сделать его более тестируемым. Конечно, это не всегда возможно, и иногда нам нужно имитировать статические методы.

5. Насмешка над статическим методом без аргументов

Давайте продолжим и посмотрим, как мы можем смоделировать метод name из нашего класса StaticUtils :

@Test
void givenStaticMethodWithNoArgs_whenMocked_thenReturnsMockSuccessfully() {
assertThat(StaticUtils.name()).isEqualTo("ForEach");

try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
utilities.when(StaticUtils::name).thenReturn("ForEach");
assertThat(StaticUtils.name()).isEqualTo("ForEach");
}

assertThat(StaticUtils.name()).isEqualTo("ForEach");
}

Как упоминалось ранее, начиная с Mockito 3.4.0, мы можем использовать метод Mockito.mockStatic( Class<T> classToMock ) для имитации вызовов статических методов. Этот метод возвращает объект MockedStatic для нашего типа, который является фиктивным объектом с ограниченной областью действия.

Поэтому в нашем вышеприведенном модульном тесте переменная утилит представляет собой макет с явной областью действия, локальной для потока. Важно отметить, что макеты с ограниченной областью должны быть закрыты сущностью, которая активирует макет. Вот почему мы определяем наш макет в конструкции try-with-resources , чтобы макет автоматически закрывался, когда мы заканчиваем работу с нашим блоком области действия.

Это особенно приятная функция, поскольку она гарантирует, что наш статический макет останется временным. Как мы знаем, если мы играем со статическими вызовами методов во время наших тестовых прогонов, это, вероятно, приведет к неблагоприятным последствиям в наших результатах тестов из-за одновременного и последовательного характера выполнения тестов.

Кроме того, еще одним приятным побочным эффектом является то, что наши тесты по-прежнему будут выполняться довольно быстро, поскольку Mockito не нужно заменять загрузчик классов для каждого теста.

В нашем примере мы повторяем этот момент, проверяя до и после нашего блока с областью действия, что имя нашего статического метода возвращает реальное значение.

6. Насмешка над статическим методом с аргументами

Теперь давайте рассмотрим еще один распространенный вариант использования, когда нам нужно имитировать метод с аргументами:

@Test
void givenStaticMethodWithArgs_whenMocked_thenReturnsMockSuccessfully() {
assertThat(StaticUtils.range(2, 6)).containsExactly(2, 3, 4, 5);

try (MockedStatic<StaticUtils> utilities = Mockito.mockStatic(StaticUtils.class)) {
utilities.when(() -> StaticUtils.range(2, 6))
.thenReturn(Arrays.asList(10, 11, 12));

assertThat(StaticUtils.range(2, 6)).containsExactly(10, 11, 12);
}

assertThat(StaticUtils.range(2, 6)).containsExactly(2, 3, 4, 5);
}

Здесь мы следуем тому же подходу, но на этот раз мы используем лямбда-выражение внутри нашего предложения when , где мы указываем метод вместе с любыми аргументами, которые мы хотим имитировать. Довольно просто!

7. Заключение

В этой быстрой статье мы увидели несколько примеров того, как мы можем использовать Mockito для имитации статических методов. Подводя итог, можно сказать, что Mockito предлагает изящное решение, использующее более узкую область видимости для имитируемых статических объектов с помощью одной небольшой лямбда-выражения.

Как всегда, полный исходный код статьи доступен на GitHub .