Как высмеять окончательный класс с 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 класс RainOnTrees . Как я могу это сделать с Mockito?

Мокширование финальных / статических классов / методов возможно только с Mockito v2.

Это невозможно с Mockito v1, из FAQ Mockito :

Каковы ограничения Mockito

  • Требуется java 1.5+

  • Нельзя издеваться над финальными классами

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()); 

В последующих этапах команда предложит программный способ использования этой функции. Мы определим и обеспечим поддержку всех несвязанных сценариев. Оставайтесь с нами и сообщите нам, что вы думаете об этой функции!

Вы не можете издеваться над финальным классом с 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 класс RainOnTrees который делегирует конечный класс. Или, если вы можете сделать это не финальным, было бы лучше.

Используйте Powermock. Эта ссылка показывает, как это сделать: https://github.com/jayway/powermock/wiki/MockFinal

У меня такая же проблема. Поскольку класс, который я пытался высмеять, был простым классом, я просто создал его экземпляр и вернул его.

Попробуйте:

 Mockito.mock(SomeMockableType.class,AdditionalAnswers.delegatesTo(someInstanceThatIsNotMockableOrSpyable)); 

Это сработало для меня. «SomeMockableType.class» – это родительский класс того, что вы хотите насмехаться или шпионить, а someInstanceThatIsNotMockableOrSpyable – это фактический класс, который вы хотите издеваться или шпионить.

Для более подробной информации смотрите здесь

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

Я думаю, вы сделали это final потому что хотите запретить другим классам распространять RainOnTrees . Как предлагает эффективная Java (пункт 15), есть еще один способ сохранить класс для расширения, не делая его final :

  1. Удалить ключевое слово final ;

  2. Сделайте свой конструктор private . Ни один класс не сможет расширить его, потому что он не сможет вызвать super конструктор;

  3. Создайте статический заводский метод для создания экземпляра вашего класса.

     // 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 и держать свой класс закрытым для расширения с небольшим шаблоном кода.

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

  1. Вы используете какой-то DI, чтобы ввести экземпляр конечного класса
  2. Заключительный класс реализует интерфейс

Пожалуйста, вспомните пункт 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(); , 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(); 

Это можно сделать, если вы используете 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)); } 

}

Надеюсь, поможет.

Полная статья, представленная здесь, насмешливо-немодущая .

Просто следить. Добавьте эту строку в свой файл gradle:

 testCompile group: 'org.mockito', name: 'mockito-inline', version: '2.8.9' 

Я пробовал различную версию mockito-core и mockito-all. Ни один из них не работает.

Посмотрите на JMockit . Он имеет обширную документацию с большим количеством примеров. Здесь у вас есть пример решения вашей проблемы (чтобы упростить, я добавил конструктор в Seasons чтобы ввести RainOnTrees экземпляр RainOnTrees ):

 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(); } } } 

Да, такая же проблема здесь, мы не можем издеваться над финальным классом с Mockito. Чтобы быть точным, Мокито не может насмехаться / шпионить за:

  • заключительные классы
  • анонимные классы
  • примитивные типы

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

Решения, предоставленные RC и Luigi R. Viggiano вместе, возможно, лучшая идея.

Хотя Мокито не может , по дизайну, высмеивать заключительные классы, подход делегации возможен . Это имеет свои преимущества:

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

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

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

В более субъективной заметке: я предпочитаю свести рамки к минимуму, поэтому для меня обычно достаточно JUnit и Mockito. Фактически, ограничение этого способа иногда заставляет меня реорганизовать и навсегда.

Как заявили другие, это не будет работать из коробки с Мокито. Я бы предложил использовать отражение, чтобы установить конкретные поля объекта, который используется тестируемым кодом. Если вы обнаружите, что делаете это много, вы можете обернуть эту функциональность в библиотеку.

В стороне, если вы являетесь одним из классов маркировки, перестаньте это делать. Я столкнулся с этим вопросом, потому что я работаю с API, где все было помечено как окончательное, чтобы предотвратить мою законную потребность в расширении (насмешка), и я хочу, чтобы разработчик не предполагал, что мне не нужно будет расширять класс.

Не пробовал финал, но для частного, используя отражение, удалял модификатор! Он должен работать и на финал.