Как смоделировать синглтон-класс enum с помощью Mockito/Powermock?
Я не уверен, как издеваться над синглтоном enum.
public enum SingletonObject{
INSTANCE;
private int num;
protected setNum(int num) {
this.num = num;
}
public int getNum() {
return num;
}
Я хотел бы заглушить getNum() в приведенном выше примере, но я не могу понять, как смоделировать фактический класс SingletonObject. Я думал, что использование Powermock для подготовки теста поможет, так как перечисления по своей сути являются окончательными.
//... rest of test code
@Test
public void test() {
PowerMockito.mock(SingletonObject.class);
when(SingletonObject.INSTANCE.getNum()).thenReturn(1); //does not work
}
Это использует PowerMockMockito 1.4.10 и Mockito 1.8.5.
5 ответов
Если вы хотите заглушить то, что возвращает INSTANCE, вы можете сделать это, но это довольно неприятно (используя отражение и манипулирование байт-кодом). Я создал и протестировал простой проект с тремя классами, используя PowerMock 1.4.12 / Mockito 1.9.0. Все занятия были в одной упаковке.
SingletonObject.java
public enum SingletonObject {
INSTANCE;
private int num;
protected void setNum(int num) {
this.num = num;
}
public int getNum() {
return num;
}
}
SingletonConsumer.java
public class SingletonConsumer {
public String consumeSingletonObject() {
return String.valueOf(SingletonObject.INSTANCE.getNum());
}
}
SingletonConsumerTest.java
import static org.junit.Assert.*;
import static org.powermock.api.mockito.PowerMockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
@RunWith(PowerMockRunner.class)
@PrepareForTest({SingletonObject.class})
public class SingletonConsumerTest {
@Test
public void testConsumeSingletonObject() throws Exception {
SingletonObject mockInstance = mock(SingletonObject.class);
Whitebox.setInternalState(SingletonObject.class, "INSTANCE", mockInstance);
when(mockInstance.getNum()).thenReturn(42);
assertEquals("42", new SingletonConsumer().consumeSingletonObject());
}
}
Призыв к Whitebox.setInternalState
заменяет INSTANCE
с фиктивным объектом, которым вы можете манипулировать в своем тесте.
Иметь интерфейс с методами, которые вы намереваетесь издеваться
public interface SingletonInterface {
int getNum();
}
Пусть enum реализует интерфейс
public enum SingletonObject implements SingletonInterface {
INSTANCE;
private int num;
protected void setNum(int num) {
this.num = num;
}
@Override
public int getNum() {
return num;
}
}
Макет интерфейса
@Test
public void test() {
SingletonInterface singleton = Mockito.mock(SingletonInterface.class);
when(singleton.getNum()).thenReturn(1); //does work
}
В дополнение к приведенному выше ответу Мэтта Лахмана создайте объектную фабрику для макета мощности в SingleTonConsumerTest.class
@ObjectFactory
public IObjectFactory getObjectFactory() {
return new org.powermock.modules.testng.PowerMockObjectFactory();
}
Это удалит Mockito cannot mock/spy following: - final classes - anonymous classes - primitive types
ошибка.
Я не уверен, что это лучший подход, но в моем случае он пока работает без проблем. Я использую mockito-core и mockito-inline (3.11.0), и я издеваюсь над своим синглтоном с помощью шпиона. Пример с вашим классом:
SingletonObject so = SingletonObject.INSTANCE;
private SingletonObject spySo = Mockito.spy(so);
@Before
public void setUp() {
Mockito.doReturn(10).when(spySo).getNum();
}
@Test
public void simpleTest() {
assertThat(spySo.getNum(), is(10));
}
Документы Mockito не являются явными в случае использования enum :
Поддержка PowerMock:
- Совместимость с JUnit 4
build.gradle (** должен работать и с Maven )
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
testImplementation 'org.mockito:mockito-inline:4.0.0'
testImplementation 'org.mockito:mockito-junit-jupiter:4.1.0'
Из доступных экспериментальных функций ( Powermock + JUnit 5 ) можно попробовать:
implementation group: 'org.powermock', name: 'powermock-module-junit5', version: '1.6.4'
testImplementation 'org.powermock:powermock-mockito-release-full:1.6.4'
перечисление:
public enum ConnectionFactory {
INSTANCE;
public Connection get() throws SQLException {
//
// Load url from configuration file ('application.yml')
//
final var connection = DriverManager.getConnection(url);
connection.setAutoCommit(false);
return connection;
}
}
Можно использовать эти подходы:
class DatabaseUnitTests {
private final ConnectionFactory connectionFactory = Mockito.spy(ConnectionFactory.INSTANCE);
//Or
@InjectMocks
private final ConnectionFactory connectionFactory = Mockito.mock(ConnectionFactory.class);
@Test
void check_is_database_connection_is_OK(){
Mockito.doReturn(mockedConnection()).when(connectionFactory).get();
// Do something
}