it-swarm.com.ru

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

У меня есть последний класс, что-то вроде этого:

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?

122
buttowski

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

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

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

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

  • Не могу издеваться над выпускными классами

...

75
user180100

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

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

135
WindRider

Вы не можете издеваться над последним классом с Mockito, как вы не можете сделать это самостоятельно.

Что я делаю, так это создаю не финальный класс, чтобы обернуть последний класс и использовать его в качестве делегата. Примером этого является TwitterFactory class, и это мой смешной класс:

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

36
Luigi R. Viggiano

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

25
Gábor Lipták

добавьте это в свой файл Gradle:

testCompile 'org.mockito:mockito-inline:2.13.0'

это конфигурация для работы mockito с последними классами

17
BennyP

Просто чтобы продолжить. Пожалуйста, добавьте эту строку в ваш файл Gradle:

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

Я пробовал различные версии мокито-ядра и мокито-все. Ни один из них не работает.

12
Michael_Zhang

Я полагаю, вы сделали это final, потому что хотите предотвратить расширение других классов RainOnTrees. Как предлагает Effective 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 и держать свой класс закрытым для расширения с помощью небольшого стандартного кода.

11
Flávio Faria

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

6
user1945457

Попробуйте это:

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

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

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

5
sakis kaliakoudas

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

4
andresp

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

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

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

  • выпускные занятия
  • анонимные занятия
  • примитивные типы

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

2
Yu Chen

Это можно сделать, если вы используете Mockito2, с новой функцией инкубации, которая поддерживает насмешку над финальными классами и методами. 

Ключевые моменты, на которые следует обратить внимание:
1. Создайте простой файл с именем «org.mockito.plugins.MockMaker» и поместите его в папку с именем «mockito-extensions». Эта папка должна быть доступна на пути к классам.
2. Содержимое файла, созданного выше, должно быть одной строкой, как указано ниже:
mock-maker-inline

Вышеуказанные два шага необходимы для активации механизма расширения мокито и использования этой функции.

Примеры классов следующие: 

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

}

}

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

Полная статья, представленная здесь издевательский .

2
ksl

Экономия времени для людей, которые сталкиваются с той же проблемой (Mockito + Final Class) на Android + Kotlin. Как и в 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

Измените свой файл Gradle. Возьмите пример отсюда: 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 
2
Ozeetee

Пожалуйста, посмотрите на JMockit . Он имеет обширную документацию с множеством примеров. Здесь у вас есть пример решения вашей проблемы (чтобы упростить, я добавил конструктор в Seasons, чтобы внедрить ложный экземпляр 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();
        }

    }
}
1
Marcin

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

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

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

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

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

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

1
Neel

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

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

0
Tom N.

Если вы пытаетесь запустить юнит-тест в папке test, наилучшее решение подойдет. Просто следуйте этому добавлению расширения.

Но если вы хотите запустить его с классом, связанным с Android, таким как контекст или действие, которое находится в папке androidtest, ответ для вас.

0
Allen Wang

Для нас это было потому, что мы исключили mockito-inline из koin-test. Один модуль gradle действительно нуждался в этом, и по причине сбой только в сборках выпуска (отладочные сборки в IDE работали) :-P

0
kenyee

Я думаю, вам нужно больше думать в принципе. Вместо этого в последнем классе вы используете его интерфейс и макет интерфейса.

За это:

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

    }
0
Serg Burlaka