Як знущатися з фінального класу з макетом


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 тепер підтримує глузування з підсумкових класів та методів.
Кент

Можливий дублікат фінального класу Mock з Mockito 2
eliasah

Відповіді:


155

Знущання з фінальних / статичних класів / методів можливе лише з Mockito v2.

додайте це у свій файл gradle:

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

Це не можливо для Mockito v1, з Mockito FAQ :

Які обмеження Mockito

  • Потреби java 1.5+

  • Не можна знущатися над заключними класами

...


2
Для Scala це не спрацювало (з модифікаціями sbt).
micseydel

2
Цього мені було недостатньо. Мені також довелося створити src / test / ресурси / mockito-extensions / org.mockito.plugins.MockMaker з "макетом-макером-вкладеним" у ньому відповідно до baeldung.com/mockito-final
micseydel

204

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
Я також отримую помилку навіть після дотримання вищезгаданого: Mockito не може глузувати / шпигувати, тому що: - заключний клас
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. За бажанням ви можете використовувати щось на зразок Ломбока @Delegateдля обробки багатьох котельних плит.
ruakh

2
@luigi Ви можете додати фрагмент коду для Junit як приклад. Я спробував створити Wrapper для мого останнього класу, але не міг зрозуміти, як це перевірити.
Неймовірно

31

додайте це у свій файл gradle:

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

це конфігурація, щоб змусити макіто працювати з заключними класами


1
Напевно, слід використовувати "testImplementation" зараз замість "testCompile". Gradle вже не любить "testCompile".
jwehrle

чудовий коментар, дякую! відредаговано на тест виконання. оригінальний коментар: 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


23

Використовуйте Powermock. Це посилання показує, як це зробити: https://github.com/jayway/powermock/wiki/MockFinal


30
Я думаю, що PowerMock - це одна з тих лікарських засобів, яка повинна виходити лише за рецептом. У розумінні: слід чітко зрозуміти, що у PowerMock багато проблем; і що використовувати його - це як крайній крайній захід; і слід уникати якомога більше.
GhostCat

1
чому ти це кажеш?
PragmaticProgrammer

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

16

Просто слідкуйте. Будь ласка, додайте цей рядок до файлу gradle:

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

Я спробував різні версії mockito-core та mockito-all. Жоден з них не працює.


1
Щоб додати до цього, одне, що я помітив, було те, що якщо ви використовуєте Powermock разом із mockito; потім додавання файла плагіна mockmaker у 'src / test / ресурси / mockito-extensions / org.mockito.plugins.MockMaker' не буде корисним для глузування з фінальних класів. Натомість додавання такої залежності, про яку згадував Michael_Zhang вище, вирішило б питання знущань над заключними класами. Також переконайтесь, що ви використовуєте Mockito 2 замість Mockito1
відключіть

12

Я думаю, ви це зробили, 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
це не працює для кінцевих методів, які з mockito 2 також можуть бути знущаються.
Łukasz Rzeszotarski

11

У мене була така ж проблема. Оскільки клас, який я намагався знущатися, був простим класом, я просто створив його примірник і повернув його.


2
Абсолютно, навіщо знущатися з простого класу? Знущання призначені для «дорогих» взаємодій: інших служб, двигунів, класів даних тощо
StripLight

3
Якщо ви створюєте примірник цього, ви не можете потім застосувати методи Mockito.verify до нього. Основне використання макетів - вміти перевірити деякі його методи.
riroo

6

Спробуйте:

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

Це працювало для мене. "SomeMockableType.class" - це батьківський клас того, що ви хочете знущатися чи шпигувати, а someInstanceThatIsNotMockableOrSpyable - це фактичний клас, з якого ви хочете знущатися чи шпигувати.

Докладніше дивіться тут


3
Слід зазначити, що делегати сильно відрізняються від знущань з рідних шпигунів. У нативного шпигуна mockito "це" у посиланні на екземпляр до самого шпигуна (тому що використовується підклас). Однак у делегаті "це" буде реальним об'єктом someInstanceThatIsNotMockableOrSpyable. Не шпигун. Таким чином, немає можливості відновити / перевірити наявність функцій самовиклику.
Dennis C

1
чи можете ви прикласти приклад?
Vishwa Ratna

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 / ресурси / mockito-extensions з назвою org.mockito.plugins.MockMaker та додати один рядок тексту:

mock-maker-inline

Mockito перевіряє каталог розширень на наявність файлів конфігурації, коли він завантажується. Цей файл дозволяє глузувати з підсумкових методів та класів.


4

Заощадження часу для людей, які стикаються з тією ж проблемою (Mockito + Final Class) на Android + Kotlin. Як і в Котліні, класи за замовчуванням є остаточними. Я знайшов рішення в одному з зразків Android Android з компонентом Архітектура. Вибір рішення тут: 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, але що ми можемо зробити, щоб це отримати на рівні бібліотеки?
Суміт Т

Ви можете трохи допрацювати? Зазвичай використовуйте фасадний малюнок для підключення до ліфтів. І знущайтеся над цими класами фасадів, щоб перевірити додаток. Таким чином нам не потрібно знущатися над будь-якими класами lib.
Ozeetee

3

Це можна зробити, якщо ви використовуєте 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));

}

}

Сподіваюся, це допомагає.

Повна стаття, представлена ​​тут, насмішкувато-нерозбірлива .


Ви повинні включити відповідь тут, а не посилатися на зовнішній сайт. Якщо процедура тривала, ви можете включити огляд.
rghome

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

1
@vCillusion - Приклад, який я показав, просто використовує API Mockito2. Використовуючи функцію відключення Mockito2, можна знущатися над фінальними класами безпосередньо без необхідності використовувати Powermock.
ksl

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

Якщо ви намагаєтеся запустити блок-тест у тестовій папці, то найкраще рішення буде чудовим. Просто дотримуйтесь його, додаючи розширення.

Але якщо ви хочете запустити його з андроїд- класом, наприклад контекстом або діяльністю, що знаходиться в папці 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за замовчуванням має бути.
ЕрікЕ

0

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


0

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

1- додайте це на рівні класу @SuppressStatucInitializationFor (значення = {ім'я класу з пакетом})
2- PowerMockito.mockStatic (classname.class) знущається з класу
3-, а потім використовуватимете заявку, коли повертаєте макетний об'єкт при виклику методу цього класу.

Насолоджуйтесь


-5

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


це не відповідає на поставлене запитання
Sanjit Kumar Mishra
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.