Как высмеять окончательный класс с mockito
У меня есть последний класс, примерно такой:
public final class RainOnTrees{
public void startRain(){
// some code here
}
}
Я использую этот класс в каком-то другом классе следующим образом:
public class Seasons{
RainOnTrees rain = new RainOnTrees();
public void findSeasonAndRain(){
rain.startRain();
}
}
и в моем тестовом классе JUnit для Seasons.java
Я хочу издеваться над классом RainOnTrees
. Как я могу сделать это с помощью Mockito?
Ответы
Ответ 1
Пересмешивать финальные/статические классы/методы возможно только в Mockito v2.
добавьте это в свой файл Gradle:
testImplementation 'org.mockito:mockito-inline:2.13.0'
Это невозможно с Mockito v1 из FAQ по Mockito:
Каковы ограничения Mockito
...
Ответ 2
Mockito 2 теперь поддерживает окончательные классы и методы!
Но пока что "инкубационная" функция. Для его активации требуются некоторые шаги, которые описаны в Что нового в Mockito 2:
Отказывание окончательных классов и методов - это инкубирование, функция выбора. Он использует комбинацию инструментария и подкласса Java-агента, чтобы включить имитацию этих типов. Поскольку это работает по-другому с нашим текущим механизмом, и у этого есть разные ограничения, и поскольку мы хотим собрать опыт и отзывы пользователей, эта функция должна быть явно активирована, чтобы быть доступной; это можно сделать с помощью механизма расширения mockito, создав файл src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
, содержащий одну строку:
mock-maker-inline
После создания этого файла Mockito автоматически использует этот новый движок, и он может сделать:
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());
В последующих этапах команда предложит программный способ использования этой функции. Мы определим и обеспечим поддержку всех несвязанных сценариев. Оставайтесь с нами и сообщите нам, что вы думаете об этой функции!
Ответ 3
Вы не можете издеваться над финальным классом с Mockito, так как вы не можете сделать это самостоятельно.
То, что я делаю, заключается в создании не заключительного класса для обертывания последнего класса и использования в качестве делегата. Примером этого является TwitterFactory
класс, и это мой макет класса:
public class TwitterFactory {
private final twitter4j.TwitterFactory factory;
public TwitterFactory() {
factory = new twitter4j.TwitterFactory();
}
public Twitter getInstance(User user) {
return factory.getInstance(accessToken(user));
}
private AccessToken accessToken(User user) {
return new AccessToken(user.getAccessToken(), user.getAccessTokenSecret());
}
public Twitter getInstance() {
return factory.getInstance();
}
}
Недостатком является то, что существует много шаблонов кода; преимущество заключается в том, что вы можете добавить некоторые методы, которые могут относиться к вашей бизнес-приложениям (например, getInstance, который принимает пользователя вместо accessToken в приведенном выше случае).
В вашем случае я бы создал не конечный класс RainOnTrees
, который делегирует конечный класс. Или, если вы можете сделать это не финальным, было бы лучше.
Ответ 4
Используйте Powermock. Эта ссылка показывает, как это сделать: https://github.com/jayway/powermock/wiki/MockFinal
Ответ 5
добавьте это в свой файл Gradle:
testImplementation 'org.mockito:mockito-inline:2.13.0'
это конфигурация для работы mockito с последними классами
Ответ 6
Просто следить. Добавьте эту строку в свой файл gradle:
testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9'
Я пробовал различные версии mockito-core и mockito-all. Ни один из них не работает.
Ответ 7
Я полагаю, вы сделали это final
потому что хотите запретить другим классам расширять RainOnTrees
. Как предлагает Effective Java (пункт 15), существует другой способ закрыть класс для расширения, не делая его final
:
-
Удалить final
ключевое слово;
-
Сделайте его конструктор private
. Ни один класс не сможет расширить его, потому что он не сможет вызвать super
конструктор;
-
Создайте статический метод фабрики для создания экземпляра вашего класса.
// No more final keyword here.
public class RainOnTrees {
public static RainOnTrees newInstance() {
return new RainOnTrees();
}
private RainOnTrees() {
// Private constructor.
}
public void startRain() {
// some code here
}
}
Используя эту стратегию, вы сможете использовать Mockito и держать свой класс закрытым для расширения с помощью небольшого стандартного кода.
Ответ 8
У меня была та же проблема. Поскольку класс, который я пытался высмеять, был простым классом, я просто создал его экземпляр и вернул его.
Ответ 9
Попробуйте:
Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable));
Это сработало для меня. "SomeMockableType.class" - это родительский класс того, что вы хотите издеваться или шпионить, а someInstanceThatIsNotMockableOrSpyable - это фактический класс, который вы хотите издеваться или шпионить.
Подробнее см. здесь
Ответ 10
Другим обходным решением, которое может применяться в некоторых случаях, является создание интерфейса, который реализуется этим окончательным классом, изменить код, чтобы использовать интерфейс вместо конкретного класса, а затем издеваться над интерфейсом. Это позволяет отделить контракт (интерфейс) от реализации (конечный класс). Конечно, если вы действительно хотите привязываться к конечному классу, это не будет применяться.
Ответ 11
На самом деле есть один способ, которым я пользуюсь для шпионажа. Это будет работать для вас, только если выполнены два предварительных условия:
- Вы используете какой-то DI, чтобы внедрить экземпляр финального класса
- Финальный класс реализует интерфейс
Пожалуйста, вспомните пункт 16 из Эффективной Java. Вы можете создать оболочку (не финальную) и переслать все вызовы экземпляру финального класса:
public final class RainOnTrees implement IRainOnTrees {
@Override public void startRain() { // some code here }
}
public class RainOnTreesWrapper implement IRainOnTrees {
private IRainOnTrees delegate;
public RainOnTreesWrapper(IRainOnTrees delegate) {this.delegate = delegate;}
@Override public void startRain() { delegate.startRain(); }
}
Теперь вы можете не только высмеивать ваш последний класс, но и следить за ним:
public class Seasons{
RainOnTrees rain;
public Seasons(IRainOnTrees rain) { this.rain = rain; };
public void findSeasonAndRain(){
rain.startRain();
}
}
IRainOnTrees rain = spy(new RainOnTreesWrapper(new RainOnTrees()) // or mock(IRainOnTrees.class)
doNothing().when(rain).startRain();
new Seasons(rain).findSeasonAndRain();
Ответ 12
Да, такая же проблема здесь, мы не можем издеваться над финальным классом с Mockito. Чтобы быть точным, Мокито не может насмехаться/шпионить за:
- заключительные классы
- анонимные классы
- примитивные типы
Но использование класса-оболочки представляется мне большой ценой, поэтому вместо этого получите PowerMockito.
Ответ 13
Это можно сделать, если вы используете Mockito2, с новой функцией инкубации, которая поддерживает насмешку над финальными классами и методами.
Ключевые моменты:
1. Создайте простой файл с именем "org.mockito.plugins.MockMaker" и поместите его в папку с именем "mockito-extensions". Эта папка должна быть доступна в пути к классам.
2. Содержимое файла, созданного выше, должно быть одной строкой, как указано ниже:
издеваться-мейкера-рядный
Вышеуказанные два шага необходимы для активации механизма расширения mockito и использования этой функции выбора.
Примеры классов следующие: -
FinalClass.java
public final class FinalClass {
public final String hello(){
System.out.println("Final class says Hello!!!");
return "0";
}
}
Foo.java
public class Foo {
public String executeFinal(FinalClass finalClass){
return finalClass.hello();
}
}
FooTest.java
public class FooTest {
@Test
public void testFinalClass(){
// Instantiate the class under test.
Foo foo = new Foo();
// Instantiate the external dependency
FinalClass realFinalClass = new FinalClass();
// Create mock object for the final class.
FinalClass mockedFinalClass = mock(FinalClass.class);
// Provide stub for mocked object.
when(mockedFinalClass.hello()).thenReturn("1");
// assert
assertEquals("0", foo.executeFinal(realFinalClass));
assertEquals("1", foo.executeFinal(mockedFinalClass));
}
}
Надеюсь, что это поможет.
Полная статья представлена здесь mocking-the-unmockable.
Ответ 14
Хранитель времени для людей, которые сталкиваются с одной проблемой (Mockito + Final Class) на Android + Kotlin. Как и в классах Котлина, окончательные по умолчанию. Я нашел решение в одном из образцов Google Android с компонентом Architecture. Решение выбрано здесь: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample
Создайте следующие аннотации:
/**
* This annotation allows us to open some classes for mocking purposes while they are final in
* release builds.
*/
@Target(AnnotationTarget.ANNOTATION_CLASS)
annotation class OpenClass
/**
* Annotate a class with [OpenForTesting] if you want it to be extendable in debug builds.
*/
@OpenClass
@Target(AnnotationTarget.CLASS)
annotation class OpenForTesting
Измените файл градиента. Пример отсюда: https://github.com/googlesamples/android-architecture-components/blob/master/GithubBrowserSample/app/build.gradle
apply plugin: 'kotlin-allopen'
allOpen {
// allows mocking for classes w/o directly opening them for release builds
annotation 'com.android.example.github.testing.OpenClass'
}
Теперь вы можете аннотировать любой класс, чтобы он был открыт для тестирования:
@OpenForTesting
class RepoRepository
Ответ 15
Посмотрите JMockit. Он имеет обширную документацию с большим количеством примеров. Здесь у вас есть пример решения вашей проблемы (чтобы упростить, я добавил конструктор в Seasons
, чтобы ввести mocked RainOnTrees
instance):
package jmockitexample;
import mockit.Mocked;
import mockit.Verifications;
import mockit.integration.junit4.JMockit;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(JMockit.class)
public class SeasonsTest {
@Test
public void shouldStartRain(@Mocked final RainOnTrees rain) {
Seasons seasons = new Seasons(rain);
seasons.findSeasonAndRain();
new Verifications() {{
rain.startRain();
}};
}
public final class RainOnTrees {
public void startRain() {
// some code here
}
}
public class Seasons {
private final RainOnTrees rain;
public Seasons(RainOnTrees rain) {
this.rain = rain;
}
public void findSeasonAndRain() {
rain.startRain();
}
}
}
Ответ 16
Решения, предоставленные RC и Luigi R. Viggiano вместе, возможно, лучшая идея.
Хотя Mockito не может, по дизайну, макету финальных классов, возможно использование подхода . Это имеет свои преимущества:
- Вы не обязаны менять свой класс на нефинал, если это то, что ваш API намеревается в первую очередь (конечные классы имеют преимущества).
- Вы тестируете возможность оформления вокруг вашего API.
В тестовом случае вы намеренно перенаправляете вызовы в тестируемую систему. Следовательно, по дизайну ваше украшение ничего не делает.
Следовательно, вы можете продемонстрировать, что пользователь может только украсить API, а не расширять его.
В более субъективной заметке:
Я предпочитаю свести рамки к минимуму, поэтому для меня обычно достаточно JUnit и Mockito. Фактически, ограничение этого способа иногда заставляет меня реорганизовать и на хорошее.
Ответ 17
Я думаю, вам нужно больше думать в принципе. Вместо этого в последнем классе вы используете его интерфейс и макет интерфейса.
За это:
public class RainOnTrees{
fun startRain():Observable<Boolean>{
// some code here
}
}
добавлять
interface iRainOnTrees{
public void startRain():Observable<Boolean>
}
и издеваться над интерфейсом:
@Before
fun setUp() {
rainService= Mockito.mock(iRainOnTrees::class.java)
'when'(rainService.startRain()).thenReturn(
just(true).delay(3, TimeUnit.SECONDS)
)
}
Ответ 18
Как заявили другие, это не будет работать из коробки с Mockito. Я бы предложил использовать отражение, чтобы установить конкретные поля объекта, который используется тестируемым кодом. Если вы обнаружите, что делаете это много, вы можете обернуть эту функциональность в библиотеке.
Как в стороне, если вы являетесь одним из классов маркировки final, прекратите это делать. Я столкнулся с этим вопросом, потому что я работаю с API, где все было помечено как окончательное, чтобы предотвратить мою законную потребность в расширении (насмешка), и я хочу, чтобы разработчик не предполагал, что мне не понадобится расширять класс.
Ответ 19
Если вы пытаетесь запустить unit-test в тестовой папке, верхнее решение в порядке. Просто следуйте за ним, добавив расширение.
Но если вы хотите запустить его с помощью связанного с Android класса, такого как контекст или активность, находящийся в папке androidtest, ответ для вас.
Ответ 20
Для нас это было потому, что мы исключили mockito-inline из koin-test. Один модуль gradle действительно нуждался в этом, и по причине сбой только в сборках выпуска (отладочные сборки в IDE работали): -P
Ответ 21
Не пробовал финал, но для частного, используя отражение, удалял модификатор! проверили дальше, он не работает для финала.