Как высмеять окончательный класс с 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?

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

Ответ 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:

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

Ответ 8

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

Ответ 9

Попробуйте:

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

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

Подробнее см. здесь

Ответ 10

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

Ответ 11

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

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

Ответ 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

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