Mockito: попытка шпионить за методом вызывает оригинальный метод

352

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

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

Проблема в том, что во второй строке myClassSpy.method1()действительно вызывается, что приводит к исключению. Единственная причина, по которой я использую mocks, заключается в том, что позже, при каждом myClassSpy.method1()вызове, реальный метод не будет вызываться и myResultsобъект будет возвращен.

MyClassявляется интерфейсом и myInstanceявляется реализацией этого, если это имеет значение.

Что мне нужно сделать, чтобы исправить это шпионское поведение?

Дейв
источник
Взгляните на это: stackoverflow.com/a/29394497/355438
Lu55

Ответы:

610

Позвольте мне процитировать официальную документацию :

Важное замечание по слежке за реальными объектами!

Иногда невозможно использовать когда (объект) для шпионов шпионов. Пример:

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
Что делать, если я использую этот метод, и мой оригинальный метод все еще вызывается? Может ли быть проблема с параметрами, которые я передаю? Вот и весь тест: вызывается метод pastebin.com/ZieY790P send
Евгений Петров
26
@ EvgeniPetrov, если ваш оригинальный метод все еще вызывается, вероятно, потому что ваш оригинальный метод является окончательным. Mockito не высмеивает финальные методы и не может предупредить вас о насмешках над финальными методами.
MarcG
1
это также возможно для doThrow ()?
Гоблины
1
да, к сожалению, статические методы непригодны для шпионажа. То, что я делаю, чтобы иметь дело со статическими методами, заключается в том, чтобы обернуть метод вокруг статического вызова и использовать doNothing или doReturn для этого метода. С синглетами или объектами scala я перемещаю ядро ​​логики в абстрактный класс, и это дает мне возможность иметь альтернативный тестовый класс impl объекта, на котором я могу создать шпиона.
Эндрю Норман,
24
А что, если метод NOT final и NOT static все еще вызывается?
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(){}
}

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

Объявление метода защищенным или публичным устраняет проблему, но это не чистое решение.

Maragues
источник
2
Я столкнулся с похожей проблемой, но метод test и package-private находились в одном пакете. Я думаю, что, возможно, у Mockito есть проблемы с закрытыми методами в целом.
Дейв
22

В моем случае, используя Mockito 2.0, мне пришлось изменить все any()параметры nullable(), чтобы заглушить реальный вызов.

ejaenv
источник
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 (). Только позже работал
Рыжман
Привет, ваше решение действительно хорошее и работает для меня. Спасибо
Дирен Соланки
16

Кажется, что ответ Томаша Нуркевича не рассказывает всей истории!

NB Mockito версия: 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 () и any () заключается в том, что isA фактически выполняет проверку типов, тогда как семейство методов any () было создано просто для того, чтобы избежать приведения типов аргумент.
Кевин Уэлкер
@KevinWelker Спасибо. И действительно, имена методов не лишены определенного самоочевидного качества. Тем не менее, я, как ни странно, не согласен с гениальными дизайнерами Mockito за недостаточную документацию. Без сомнения, мне нужно прочитать еще одну книгу о Мокито. PS на самом деле ресурсов для преподавания "среднего мокито" очень мало!
Майк Грызун
1
История заключается в том, что методы anyXX были созданы первыми, чтобы иметь дело только с типизацией. Затем, когда было предложено добавить проверку аргументов, они не хотели ломать пользователей существующего API, поэтому они создали семейство isA (). Зная, что методы any () должны были выполнять проверку типов все время, они откладывали их изменение до тех пор, пока не внесли другие критические изменения в капитальный ремонт Mockito 2.X (который я еще не пробовал). В 2.x + методы anyX () являются псевдонимами для методов isA ().
Кевин Уэлкер,
Спасибо. Это ключевой ответ для тех из нас, кто делает несколько обновлений библиотеки одновременно, потому что код, который раньше выполнялся, внезапно и молча дает сбой.
Декс Стаккер
6

Еще один возможный сценарий , который может вызывают проблемы со шпионами, когда вы тестируете весной боб (с пружинными тестовой структурой) или какой - либо другой структурой, которая proxing ваших объектов во время испытания .

пример

@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()работал.

Matruskan
источник
это было причиной в моем случае, я пытался издеваться над последним методом, но он продолжал вызывать реальный без четкого сообщения об ошибке, которое сбивало с толку.
Башар Али Лабади
1

Немного опоздал на вечеринку, но вышеприведенные решения у меня не сработали, поэтому делюсь своими 0,02 $

Версия Mokcito: 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");
            };
        });
Джеффри Ричи
источник
-1

Ответ для пользователей scala: даже не ставить на doReturnпервое место не получится! Смотрите этот пост .

Ник Резник
источник
это не ответ
Умпа