Мокіто: спроба шпигувати за методом викликає оригінальний метод


351

Я використовую Mockito 1.9.0. Я хочу знущатися над поведінкою для одного методу класу в тесті JUnit, тому в мене є

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

Проблема полягає у тому, що у другому рядку myClassSpy.method1()насправді викликають, що призводить до винятку. Єдина причина, по якій я використовую макети, полягає в тому, що пізніше, коли він myClassSpy.method1()буде викликаний, справжній метод не буде викликаний і myResultsоб'єкт буде повернуто.

MyClassє інтерфейсом і myInstanceє реалізацією цього, якщо це має значення.

Що мені потрібно зробити, щоб виправити цю шпигунську поведінку?


Подивіться на це: stackoverflow.com/a/29394497/355438
Lu55

Відповіді:


610

Дозвольте мені навести офіційну документацію :

Важлива прихильність до шпигування реальних об'єктів!

Іноді це неможливо використовувати, коли (Object) для заглушення шпигунів. Приклад:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

У вашому випадку це щось на зразок:

doReturn(resulstIWant).when(myClassSpy).method1();

27
Що робити, якщо я використовую цей метод, і моїм оригінальним є ПОСЛУГИ викликати? Чи може виникнути проблема з параметрами, які я передаю? Ось весь тест: метод callbin pastebin.com/ZieY790P send
Євгеній Петров

26
@EvgeniPetrov, якщо ваш оригінальний метод все ще називається, можливо, тому що ваш оригінальний метод остаточний. Mockito не знущається над кінцевими методами і не може попередити вас про глузування з фінальних методів.
MarcG

1
це також можливо для doThrow ()?
Гоблінс

1
так, на жаль, статичні методи неможливі для неможливості підключення та "неможливості шпигуна". Що я маю для того, щоб мати справу зі статичними методами, це обернути метод навколо статичного виклику і використовувати doNothing або doReturn для цього методу. За допомогою одиночних або масштабних об'єктів я переміщую м'ясо логіки до абстрактного класу, і це дає мені можливість мати альтернативний тестовий клас impl об'єкта, на який я можу створити шпигуна.
Ендрю Норман

24
А що робити, якщо НЕ остаточний і НЕ статичний метод все ще називають?
X-HuMan

27

Мій випадок відрізнявся від прийнятої відповіді. Я намагався знущатися з методом пакет-приватний для екземпляра, який не жив у цьому пакеті

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

і тестові класи

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

Компіляція правильна, але коли вона намагається встановити тест, вона замість цього викликає реальний метод.

Оголошення методу захищеним або загальнодоступним вирішує проблему, тому це не є чистим рішенням.


2
Я зіткнувся з подібною проблемою, але тестовий і приватно-пакетний метод були в одному пакеті. Я думаю, що, можливо, у Mockito взагалі є проблеми з приватно-приватними методами.
Дейв

22

У моєму випадку, використовуючи Mockito 2.0, мені довелося змінити всі any()параметри, щоб заглушити nullable()реальний дзвінок.


2
Не дозволяйте, щоб 321 проголосований найкращий відповідь звів вас, це вирішило мою проблему :) Я боровся з цим пару годин!
Кріс Кессел

3
Це була відповідь для мене. Щоб зробити це ще простішим для тих, хто слідує, коли знущаються над вашим методом, синтаксис такий: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder

З Mockito 2.23.4 я можу підтвердити, що це не обов'язково, він добре працює з anyі відповідає eq.
vmaldosan

2
Спробували три різні версії на версії 2.23.4 lib: any (), eq () та nullable (). Лише пізніше працював
рижман

Привіт, ваше рішення дуже приємне і працювало і для мене. Спасибі
Dhiren Solanki

16

Відповідь Томаша Нуркевича, здається, не розповідає всієї історії!

NB Версія Мокіто: 1.10.19.

Я дуже новачок Mockito, тому не можу пояснити таку поведінку: якщо там є експерт, який може покращити цю відповідь, будь ласка, не соромтеся.

Метод, про який йдеться тут getContentStringValue, - НЕ final і НЕ static .

Цей рядок робить викликати оригінальний метод getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

Цей рядок не викликає оригінальний метод getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

З причин, на які я не можу відповісти, використання isA()причин невдалого (?) Поведінки "не викликати метод" doReturn.

Давайте розглянемо метод підписів, які тут беруть участь: вони обидва staticметоди Matchers. Обидва кажуть Javadoc повернутися null, що важко обійти голову в собі. Імовірно, Classоб'єкт, переданий як параметр, перевіряється, але результат ніколи не обчислюється або відкидається. З огляду на те, що nullможе стояти будь-який клас, і ви сподіваєтесь, що не буде викликаний знущається метод, чи не могли підписи isA( ... )та any( ... )просто повернутись, nullа не загальний параметр * <T>?

У будь-якому випадку:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

Документація API не дає жодної підказки про це. Також, здається, йдеться про необхідність такої поведінки "не називати метод", "дуже рідко". Особисто я весь час використовую цю техніку : зазвичай я вважаю, що глузування включає кілька рядків, які "задають сцену" ... з подальшим викликом методу, який потім "розігрує" сцену в макетному контексті, який ви поставили. і поки ви налаштовуєте декорації та реквізити, останнє, що вам потрібно, це актори вийти на сцену ліворуч і почати виконувати свої думки ...

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

* це "загальний параметр" правильний термін?


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

@KevinWelker Дякую І справді, іменам методів не вистачає певної якості, що пояснюється. Я, однак, хоч і м'яко кажучи, сприймаю питання з геніальними дизайнерами Mockito за те, що вони не мали документально адекватну документацію. Без сумніву, мені потрібно прочитати ще одну книгу про Мокіто. PS насправді, здається, дуже мало ресурсів для навчання "проміжного Мокіто"!
мійський гризун

1
Історія полягає в тому, що методи anyXX були створені спочатку як спосіб розібратися лише з набором тексту. Потім, коли їм запропонували додати перевірку аргументів, вони не хотіли ламати користувачів існуючого API, тому вони створили сімейство isA (). Знаючи, що будь-які () методи повинні були робити перевірку типу протягом усього часу, вони затримували зміни цих, поки вони не внесли інші переломні зміни в капітальний ремонт Mockito 2.X (чого я ще не пробував). У 2.x + методи anyX () є псевдонімами методів isA ().
Кевін Велкер

Дякую. Це головна відповідь для тих, хто робить кілька оновлень бібліотеки одночасно, тому що код, який працював для запуску, раптово і безшумно виходить з ладу.
Dex Stakker

6

Ще один можливий сценарій, який може спричинити проблеми зі шпигунами, - це тестування ярої квасолі (із рамкою весняного тестування) або якоїсь іншої рамки, яка наближається до ваших об'єктів під час тесту .

Приклад

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

У наведеному вище коді і Spring, і Mockito намагатимуться прокситувати ваш об’єкт MonitoringDocumentsRepository, але Spring буде першим, що спричинить реальний виклик методу findMonitoringDocuments. Якщо ми налагодимо наш код відразу після розміщення шпигуна на об’єкт сховища, він буде виглядати приблизно так: всередині налагоджувача:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean на допомогу

Якщо замість @Autowiredанотації ми використовуємо @SpyBeanанотацію, ми вирішимо вищезазначену проблему, анотація SpyBean також буде вводити об'єкт сховища, але він буде спочатку проксі-сервісом Mockito і буде виглядати як всередині налагоджувача

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

і ось код:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

1

Я знайшов ще одну причину для шпигуна викликати оригінальний метод.

У когось виникла ідея знущатися над уроком finalі дізналася про MockMaker:

Оскільки це працює по-різному від нашого нинішнього механізму, і цей має різні обмеження, і оскільки ми хочемо збирати досвід та відгуки користувачів, цю функцію потрібно було чітко активувати, щоб бути доступною; це можна зробити через механізм розширення mockito, створивши файл, src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerщо містить один рядок:mock-maker-inline

Джерело: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Після того як я об'єднав і приніс цей файл на свою машину, мої тести не вдалися.

Я просто повинен був видалити рядок (або файл), і spy()працював.


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

1

Трохи спізнившись на вечірку, але вищевикладені рішення для мене не спрацювали, тому поділившись 0,02 $

Версія Моккіто: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

Далі НЕ працював для мене (фактичний метод викликався):

1.

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2.

doReturn(0).when(spy , "handleAction", any(), anyString());

3.

doReturn(0).when(spy , "handleAction", null, null);

Наступні РОБОТИ:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());

0

Один із способів переконатись, що метод з класу не викликається - це перекриття методу з манекеном.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });

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