Финальный метод издевательства
Мне нужно смоделировать некоторый класс с финальным методом, используя mockito. Я написал что-то вроде этого
@Test
public void test() {
B b = mock(B.class);
doReturn("bar called").when(b).bar();
assertEquals("must be \"overrided\"", "bar called", b.bar());
//bla-bla
}
class B {
public final String bar() {
return "fail";
}
}
Но это не удается. Я попробовал некоторые "взломать", и это работает.
@Test
public void hackTest() {
class NewB extends B {
public String barForTest() {
return bar();
}
}
NewB b = mock(NewB.class);
doReturn("bar called").when(b).barForTest();
assertEquals("must be \"overrided\"", "bar called", b.barForTest());
}
Работает, но "пахнет".
Так где же правильный путь?
Благодарю.
9 ответов
В Mockito не поддерживается насмешка над финальными методами.
Как прокомментировал Джон Скит, вы должны искать способ избежать зависимости от финального метода. Тем не менее, есть некоторые выходы через манипулирование байт-кодом (например, с PowerMock)
Сравнение между Mockito и PowerMock объяснит все подробно.
Каковы ограничения Mockito
- Не может издеваться над конечными методами - их реальное поведение выполняется без каких-либо исключений. Мокито не может предупредить вас о насмешливых окончательных методах, поэтому будьте бдительны.
Вы можете использовать Powermock вместе с Mockito, тогда вам не нужно создавать подкласс B.class. Просто добавьте это в начало вашего тестового класса
@RunWith(PowerMockRunner.class)
@PrepareForTest(B.class)
@PrepareForTest
инструктирует Powermock на инструмент B.class, чтобы сделать финальный и статический методы надёжными. Недостаток этого подхода заключается в том, что вы должны использовать PowerMockRunner, что исключает использование других тестеров, таких как тестер Spring.
Mockito 2 теперь поддерживает финальные методы насмешки, но это "инкубационная" функция. Требуется несколько шагов для его активации, которые описаны здесь: https://github.com/mockito/mockito/wiki/What's-new-in-Mockito-2
Mockito 2.x теперь поддерживает метод final и последний класс.
Насмешка над итоговыми классами и методами - это инкубационная функция. Эта функция должна быть явно активирована путем создания файла
src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
содержащий одну строку:
mock-maker-inline
После создания этого файла вы можете сделать:
final class FinalClass { final String finalMethod() { return "something"; } } FinalClass concrete = new FinalClass(); FinalClass mock = mock(FinalClass.class); given(mock.finalMethod()).willReturn("not anymore"); assertThat(mock.finalMethod()).isNotEqualTo(concrete.finalMethod());
На последующих этапах команда представит программный способ использования этой функции. Мы определим и предоставим поддержку для всех немыслимых сценариев.
Предполагая, что класс B такой, как показано ниже:
class B {
private String barValue;
public final String bar() {
return barValue;
}
public void final setBar(String barValue) {
this.barValue = barValue;
}
}
Есть лучший способ сделать это без использования инфраструктуры PowerMockito. Вы можете создать SPY для своего класса и высмеивать ваш последний метод. Ниже представлен способ сделать это:
@Test
public void test() {
B b = new B();
b.setBar("bar called") //This should the expected output:final_method_bar()
B spyB = Mockito.spy(b);
assertEquals("bar called", spyB.bar());
}
Mockito можно использовать для имитации конечных классов или конечных методов. Проблема в том, что это не стандартная функция Mockito, и ее необходимо настраивать явно.
Итак, чтобы сделать это,
Создайте текстовый файл с именем
org.mockito.plugins.MockMaker
к проекту
src/test/resources/mockito-extensions
каталог и добавьте одну строку текста, как показано ниже
mock-maker-inline
После этого вы можете использовать метод when мокито, чтобы имитировать поведение, как и любой другой обычный метод.
Подробные примеры смотрите здесь
Если вы не хотите использоватьmock-maker-inline
илиPowerMock
(как в моем случае, когда их использование привело к сбою некоторых других тестов монолитного приложения), вы можете сделать это:
- Оберните вызовы методов финального класса или финальных методов в защищенные методы внутри тестируемого класса.
-
@Spy
класс, который вы хотите протестировать, и имитировать защищенные методы, созданные вами в № 1.
Обратитесь к ним для издевательства над методами тестируемого класса: издевательства над методами в том же классе и «Как издеваться над другим методом в том же тестируемом классе?»
Я просто сделал то же самое. В моем случае я хотел убедиться, что метод не "вызывает" ошибку, но так как это метод catch / log / return, я не смог проверить его напрямую, не изменив класс.
Я хотел просто издеваться над логгером, который я передал, но что-то насмешливое над интерфейсом "Log", похоже, не сработало, и насмешка над классом вроде "SimpleLog" не сработала, потому что эти методы окончательны.
Я закончил тем, что создал анонимный внутренний класс, расширяющий SimpleLog, который переопределяет метод "log (level, string, error)" базового уровня, которому делегируются все остальные, а затем просто ждал вызова с "level" 5.
В общем, расширение класса поведения не является плохой идеей, в любом случае может быть предпочтительнее насмешки, если это не слишком сложно.