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


218

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

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?


9
Mockito не позволяет этого, однако PowerMock делает.
— fge

1
Начиная с Mockito 2.x, Mockito теперь поддерживает моделирование конечных классов и методов.
— Кент,

Ответы:


156

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

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

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

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

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

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

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

...


2
Это не сработало для меня в Scala (с модификациями sbt).
— micseydel

2
Это было недостаточно для меня. Я также должен был создать src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker с «mock-maker-inline» в нем, как указано
— micseydel

206

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

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


14
Я все еще получаю сообщение об ошибке: Не могу смутить / шпионить класс android.content.ComponentName Mockito не может смоделировать / шпионить, потому что: - последний класс
— Игорь Ганапольский

3
Убедитесь, что вы поместили org.mockito.plugins.MockMakerфайл в правильную папку.
— WindRider

7
Я также получаю сообщение об ошибке даже после выполнения вышесказанного: Мокито не может издеваться / шпионить, потому что: - последний класс
— rcde0

8
@vCillusion этот ответ никак не связан с PowerMock.
— Линия

6
Я следовал этим инструкциям, но я все еще не мог сделать эту работу, кто-нибудь должен был делать что-то еще?
— Франко

43

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


6
+1. При желании вы можете использовать что-то вроде Lombok @Delegateдля обработки большого количества шаблонов.
— Руах

2
@luigi вы можете добавить фрагмент кода для Junit в качестве примера. Я пытался создать Wrapper для моего последнего класса, но не мог понять, как это проверить.
— Невероятно,

31

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

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

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


1
Вероятно, теперь следует использовать «testImplementation» вместо «testCompile». Gradle больше не любит testCompile.
— jwehrle

отличный комментарий, спасибо! отредактировано до testImplementation. оригинальный комментарий: testCompile 'org.mockito: mockito-inline: 2.13.0'
— BennyP

2
Это приводит к ошибке при работе в Linux / OpenJDK 1.8:org.mockito.exceptions.base.MockitoInitializationException: Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
— naXa

Работает нормально при переключении на Oracle JDK 1.8
— naXa

23

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


30
Я думаю, что PowerMock - это одно из тех лекарств, которое следует принимать только по рецепту. В том смысле, что нужно четко понимать, что у PowerMock много проблем; и что его использование похоже на последнее средство; и следует избегать как можно больше.
— GhostCat

1
Почему ты это сказал?
— PragmaticProgrammer

Я использовал Powermockдля насмешки над финальными классами и статическими методами, чтобы увеличить мой охват, который был официально проверен Sonarqube. После SonarQube охват составил 0%, по какой-либо причине не распознает классы, которые используют Powermock где-либо внутри него. Я потратил некоторое время на то, чтобы осознать это в своей сети. Так что это только одна из причин быть осторожным с Powermock и, вероятно, не использовать его.
— амер

16

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

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

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


1
Чтобы добавить к этому, я заметил, что если вы используете Powermock вместе с mockito; тогда добавление файла плагина mockmaker в 'src / test / resources / mockito-extensions / org.mockito.plugins.MockMaker' не будет полезным для насмешки над конечными классами. Вместо этого добавление зависимости, подобной упомянутой выше Michael_Zhang, решило бы проблему насмешки над финальными классами. Кроме того, убедитесь, что вы используете Mockito 2 вместо Mockito1
— dishooom

12

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


1
это не работает для окончательных методов, которые с mockito 2 также можно смоделировать.
— Лукаш

11

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


2
Абсолютно, зачем издеваться над простым классом? Пересмешка для «дорогих» взаимодействий: другие сервисы, движки, классы данных и т. Д.
— StripLight

3
Если вы создадите экземпляр этого, вы не сможете применить к нему методы Mockito.verify впоследствии. Основное использование макетов - уметь тестировать некоторые из его методов.
— riroo

6

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

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

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

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


3
Следует заметить, что делегаты сильно отличаются от насмешек над родными шпионами. В нативном шпионе-мокито «this» в экземпляре ссылается на сам шпион (поскольку это подкласс использования). Однако в делегате «this» будет реальным объектом someInstanceThatIsNotMockableOrSpyable. Не шпион. Таким образом, нет способа сделать Возврат / Проверка для функций, вызывающих себя.
— Деннис C

1
Вы можете привести пример?
— Вишва Ратна

5

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


5

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

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

5

В Mockito 3 и более у меня такая же проблема и исправлена ​​как по этой ссылке

Пробные финальные классы и методы с Mockito следующим образом

Прежде чем Mockito можно будет использовать для макетирования окончательных классов и методов, его необходимо> настроить.

Нам нужно добавить текстовый файл в каталог проекта src / test / resources / mockito-extensions с именем org.mockito.plugins.MockMaker и добавить одну строку текста:

mock-maker-inline

Mockito проверяет каталог расширений на наличие файлов конфигурации при его загрузке. Этот файл включает макетирование финальных методов и классов.


4

Экономия времени для людей, которые сталкиваются с той же проблемой (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 

Это хорошо работает на уровне приложения build.gradle, но что мы можем сделать, чтобы получить это на уровне библиотеки?
— Sumit T

Можете ли вы уточнить немного? Обычно для подключения к библиотекам используйте шаблон фасада. И издевайтесь над этими классами фасадов, чтобы протестировать приложение. Таким образом, нам не нужно высмеивать какие-либо классы lib.
— Ozeetee

3

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

}

}

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

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


Вы должны включить ответ здесь, а не ссылку на внешний сайт. Если процедура длинная, вы можете включить обзор.
— rghome

пожалуйста, убедитесь, что ниже аннотации используются при насмешливом @RunWith (PowerMockRunner.class) @PrepareForTest ({AFinalClass.class})
— vCillusion

1
@vCillusion - В примере, который я показал, используется только API-интерфейс Mockito2. Используя функцию согласия Mockito2, можно напрямую смоделировать финальные классы, не используя Powermock.
— 17

2

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

  • последние занятия
  • анонимные занятия
  • примитивные типы

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


2

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

Для этого:

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

    }

1

Пожалуйста, посмотрите на 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

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

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

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

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

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

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


1

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

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


1

Добавьте эти зависимости для успешного запуска mockito:

testImplementation 'org.mockito: mockito-core: 2.24.5'
testImplementation "org.mockito: mockito-inline: 2.24.5"


0

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

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


1
Публичные классы API должны быть открыты для расширения. Полностью согласен. Тем не менее, в частной кодовой базе, finalдолжно быть по умолчанию.
— ErikE

0

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


0

Для финального класса добавьте ниже к макету и назовите статический или нестатический.

1 - добавьте это на уровне класса @SuppressStatucInitializationFor (value = {имя класса с пакетом})
2 - PowerMockito.mockStatic (classname.class) будет имитировать класс
3 - затем используйте оператор when для возврата фиктивного объекта при вызове метода этого класса.

наслаждаться


-5

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


это не отвечает на заданный вопрос
— Санджит Кумар Мишра
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.