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


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.