В чем разница между насмешкой и шпионажем при использовании Mockito?

Каким будет использование шпионского ПО Mockito?

Мне кажется, что каждый случай использования шпиона можно обрабатывать с помощью mock, используя callRealMethod.

Единственное различие, которое я вижу, - это то, что вы хотите, чтобы большинство вызовов методов были реальными, он сохраняет некоторые строки кода для использования макета и шпиона. Это или я пропущу более крупную картину?

Ответ 1

Ответ в документации:

Real partial mocks (Since 1.8.0)

Наконец, после многих внутренних дискуссий & обсуждения в списке рассылки, частичная поддержка mock была добавлена в Mockito. Ранее мы рассматривали частичные насмешки как запахи кода. Однако мы нашли законный вариант использования для частичных имитаций.

До релиза 1.8 spy() не производил реальных частичных насмешек, и это приводило некоторых в замешательство. Подробнее о шпионаже: здесь или в javadoc for spy (Object).

callRealMethod() был введен после spy(), но spy() был оставлен там, конечно, для обеспечения обратной совместимости.

В противном случае, вы правы: все методы шпиона реальны, если не зарезаны. Все методы макета заглушаются, если не вызывается callRealMethod(). В целом, я бы предпочел использовать callRealMethod(), потому что это не заставляет меня использовать идиому doXxx().when() вместо традиционного when().thenXxx()

Ответ 2

Разница между шпионом и макетом

Когда Mockito создает макет - он делает это из класса типа, а не из фактического экземпляра. Макет просто создает экземпляр оболочки bare-bones для класса, который полностью предназначен для отслеживания взаимодействий с ним. С другой стороны, шпион обернет существующий экземпляр. Он по-прежнему будет вести себя так же, как обычный экземпляр - единственное различие заключается в том, что он также будет использоваться для отслеживания всех взаимодействий с ним.

В следующем примере - мы создаем макет класса ArrayList:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Как вы можете видеть - добавление элемента в издевательский список фактически ничего не добавляет - он просто вызывает метод без какого-либо другого побочного эффекта. С другой стороны, шпион будет вести себя по-другому - он фактически вызовет реальную реализацию метода добавления и добавит элемент в основной список:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Здесь мы можем с уверенностью сказать, что реальный внутренний метод объекта был вызван, потому что, когда вы вызываете метод size(), вы получаете размер как 1, но этот метод size() не издевается! Итак, откуда это получается? Внутренний метод real size() называется size(), не издевается (или не затуманивается), и поэтому мы можем сказать, что запись была добавлена ​​к реальному объекту.

Источник: http://www.baeldung.com/mockito-spy + собственные заметки.

Ответ 3

Если есть объект с 8 методами, и у вас есть тест, в котором вы хотите вызвать 7 реальных методов и заглушить один метод, у вас есть два варианта:

  • Используя макет, вам придется настроить его, вызвав 7 callRealMethod и опустить один метод.
  • Используя spy, вы должны установить его, выполнив один метод

Официальная документация в doCallRealMethod рекомендует использовать шпион для частичных mocks.

См. также javadoc spy (Object), чтобы узнать больше о частичных маках. Mockito.spy() - рекомендуемый способ создания частичных mocks. причина в том, что он гарантирует, что реальные методы вызывают против правильно построенный объект, потому что вы несете ответственность за объект передан методу spy().

Ответ 4

Шпион может быть полезен, когда вы хотите создать модульные тесты для устаревшего кода.

Я создал работоспособный пример здесь https://www.surasint.com/mockito-with-spy/, я скопировал некоторые из них здесь.

Если у вас есть что-то вроде этого кода:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Возможно, вам не нужен шпион, потому что вы можете просто издеваться над DepositMoneyService и WithdrawMoneyService.

Но с некоторыми устаревшими кодами зависимость находится в следующем коде:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Да, вы можете перейти к первому коду, но затем API будет изменен. Если этот метод используется во многих местах, вы должны изменить все из них.

Альтернативой является то, что вы можете извлечь зависимость следующим образом:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Затем вы можете использовать шпион для внедрения зависимости следующим образом:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

Подробнее в ссылке выше.