Вопрос-ответ

Mocking static methods with Mockito

Имитация статических методов с помощью Mockito

Я написал фабрику для создания java.sql.Connection объектов:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

@Override public Connection getConnection() {
try {
return DriverManager.getConnection(...);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

Я хотел бы проверить параметры, переданные DriverManager.getConnection, но я не знаю, как имитировать статический метод. Я использую JUnit 4 и Mockito для своих тестовых примеров. Есть ли хороший способ имитировать / проверить этот конкретный вариант использования?

Переведено автоматически
Ответ 1

Используйте PowerMockito поверх Mockito.

Пример кода:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

@Test
public void shouldVerifyParameters() throws Exception {

//given
PowerMockito.mockStatic(DriverManager.class);
BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

//when
sut.execute(); // System Under Test (sut)

//then
PowerMockito.verifyStatic();
DriverManager.getConnection(...);

}

Дополнительная информация:

Ответ 2

Имитация статических методов в Mockito возможна начиная с Mockito 3.4.0. Для получения более подробной информации см.:

https://github.com/mockito/mockito/releases/tag/v3.4.0

https://github.com/mockito/mockito/issues/1013

https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks

assertEquals("foo", Foo.method());
try (MockedStatic mocked = mockStatic(Foo.class)) {
mocked.when(Foo::method).thenReturn("bar");
assertEquals("bar", Foo.method());
mocked.verify(Foo::method);
}
assertEquals("foo", Foo.method());

В вашем случае, что-то вроде этого:

  @Test
public void testStaticMockWithVerification() throws SQLException {
try (MockedStatic<DriverManager> dummy = Mockito.mockStatic(DriverManager.class)) {
DatabaseConnectionFactory factory = new MySQLDatabaseConnectionFactory();
dummy.when(() -> DriverManager.getConnection("arg1", "arg2", "arg3"))
.thenReturn(new Connection() {/*...*/});

factory.getConnection();

dummy.verify(() -> DriverManager.getConnection(eq("arg1"), eq("arg2"), eq("arg3")));
}
}

(До Mockito 5.0.0 имитирующие статические методы требовали дополнительной зависимости от mockito-inline)

Для JUnit5 также добавьте это:

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
Ответ 3

Типичная стратегия уклонения от статических методов, от использования которых у вас нет возможности избежать, заключается в создании обернутых объектов и использовании вместо них объектов-оболочек.

Объекты-оболочки становятся фасадами реальных статических классов, и вы их не тестируете.

Объект-оболочка может быть чем-то вроде

public class Slf4jMdcWrapper {
public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
return MDC.getWhateverIWant();
}
}

Наконец, ваш тестируемый класс может использовать этот одноэлементный объект, например, за счет
наличия конструктора по умолчанию для реального использования:

public class SomeClassUnderTest {
final Slf4jMdcWrapper myMockableObject;

/** constructor used by CDI or whatever real life use case */
public myClassUnderTestContructor() {
this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
}

/** constructor used in tests*/
myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
this.myMockableObject = myMock;
}
}

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

Если вы используете CDI и можете использовать аннотацию @Inject, то это еще проще. Просто создайте свой компонент-оболочку @ApplicationScoped, внедрите эту штуку в качестве соавтора (вам даже не нужны беспорядочные конструкторы для тестирования) и продолжайте имитировать.

Ответ 4

У меня была похожая проблема. Принятый ответ у меня не работал, пока я не внес изменение: @PrepareForTest(TheClassThatContainsStaticMethod.class) согласно документации PowerMock для mockStatic.

И мне не нужно использоватьBDDMockito.

Мой класс:

public class SmokeRouteBuilder {
public static String smokeMessageId() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("Exception occurred while fetching localhost address", e);
return UUID.randomUUID().toString();
}
}
}

Мой тестовый класс:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SmokeRouteBuilder.class)
public class SmokeRouteBuilderTest {
@Test
public void testSmokeMessageId_exception() throws UnknownHostException {
UUID id = UUID.randomUUID();

mockStatic(InetAddress.class);
mockStatic(UUID.class);
when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
when(UUID.randomUUID()).thenReturn(id);

assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
}
}
java unit-testing