it-swarm.com.ru

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

Я использую 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 - реализация этого, если это имеет значение.

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

256
Dave

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

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

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

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();
461
Tomasz Nurkiewicz

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

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(){}
}

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

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

24
Maragues

Ответ Томаша Нуркевича, кажется, не говорит всю историю!

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 не дает никакой подсказки по этому поводу. Также кажется, что потребность в таком поведении «не вызывать метод» является «очень редкой». Лично я использую эту технику все время : обычно я нахожу, что насмешка включает в себя несколько строк, которые "устанавливают сцену" ... с последующим вызовом метода, который затем "воспроизводит" сцену в контексте имитации поставили ... и пока вы настраиваете декорации и декорации, последнее, что вам нужно, это чтобы актеры вышли на сцену слева и начали разыгрывать свои сердца ...

Но это намного выше моей зарплаты ... Я приглашаю объяснения от любых проходящих первосвященников Мокито ...

* Является ли "универсальный параметр" правильным термином?

14
mike rodent

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

12
ejaenv

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

Пример

@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
Adrian Kapuscinski

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

У кого-то возникла идея смоделировать класс 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() работал.

0
Matruskan

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

0
Nick Resnick

Один из способов убедиться, что метод из класса не вызывается, - переопределить метод с помощью фиктивной переменной. 

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