Mockito - шпион против макета

Mockito - я понимаю, что шпион называет реальные методы на объекте, а макет вызывает методы на двойном объекте. Также следует избегать шпионов, если нет запаха кода. Однако как работают шпионы, и когда я должен их использовать? Как они отличаются от насмешек?

Ответ 1

Технически говоря, "издевательства" и "шпионы" - это особый тип "тестовых удвоений".

Mockito, к сожалению, делает различие странным.

Макет в mockito - это нормальный макет в других насмешливых фреймворках (позволяет вам заглушать вызовы, то есть возвращать определенные значения из вызовов методов).

Шпион в mockito является частичным mock в других насмешливых фреймворках (часть объекта будет издеваться, а часть будет использовать реальные вызовы метода).

Ответ 2

Я создал примерный пример здесь 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();

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

Ответ 3

TL; версия DR,

С mock он создает для вас экземпляр оболочки bare-bone.

List<String> mockList = Mockito.mock(ArrayList.class);

С шпионом вы можете частично издеваться над существующим экземпляром

List<String> spyList = Mockito.spy(new ArrayList<String>());

Типичный пример использования для Spy: класс имеет параметризованный конструктор, вы хотите сначала создать объект.

Ответ 4

Лучшее место для запуска - это, вероятно, документы для mockito.

В общем случае mockito mock позволяет создавать заглушки.

Вы создадите метод заглушки, если, например, этот метод выполняет дорогостоящую операцию. Скажем, он получает соединение с базой данных, извлекает значение из базы данных и возвращает его вызывающему. Получение соединения db может занять 30 секунд, что замедлит выполнение теста до того момента, когда вы, вероятно, переключитесь на контекст (или прекратите выполнение теста).

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

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

Полезно, если вы тестируете метод, который работает через побочные эффекты, тогда вы будете использовать шпиона из mockito. Эти делегаты обращаются к реальному объекту и позволяют проверять вызов метода, количество раз вызывается и т.д.

Ответ 5

Оба могут использоваться для издевательства методов или полей. Разница в том, что в макете вы создаете полный макет или фальшивый объект во время шпиона, есть реальный объект, и вы просто шпионируете или натыкаете на него определенные методы.

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

Рассмотрим пример ниже в качестве сравнения.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

Когда вы используете макет или шпион? Если вы хотите быть в безопасности и избегать вызова внешних служб и просто хотите протестировать логику внутри устройства, используйте макет. Если вы хотите вызвать внешнюю службу и выполнить вызов реальной зависимости, или просто сказать, что вы хотите запустить программу, как она есть, и просто заглушить конкретные методы, а затем использовать шпион. Так что разница между шпионом и макетом в mockito.

Ответ 6

Мне нравится простота этой рекомендации:

  • Если вы хотите быть в безопасности и избегать вызова внешних служб и просто хотите протестировать логику внутри устройства, используйте макет.
  • Если вы хотите вызвать внешнюю службу и выполнить вызов реальных зависимостей, или просто сказать, что вы хотите запустить программу как есть, и просто заглушите конкретные методы, а затем используйте шпион.

Источник: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/