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


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.