Mockito - @Spy против @Mock

99

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

Абхинав
источник
2
возможно дубликат Mockito издеваться против шпиона
Rds
Возможный дубликат Mocking vs. Spying в фреймворках
mocking

Ответы:

88

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

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

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

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

Сумасшедший
источник
41

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

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

Рассмотрим приведенный ниже пример для сравнения.

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));
    }
}

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

Ву Чыонг
источник
Хороший ответ, но он вызовет verify () только при ложной ошибке и не будет запускать тесты, если вы не инициализируете свои списки в методе @Before setUp (), как здесь mockList = mock (ArrayList.class); spyList = шпион (ArrayList.class); и удалите предлагаемую здесь аннотацию mock and spy. Я его протестировал, и сейчас мои тесты проходят.
The_Martian 01
17

TL; версия DR,

С помощью mock он создает для вас экземпляр оболочки с голой костью.

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

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

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

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

дель Бао
источник
14

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

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

Сурасин Танчароен
источник
13

Лучше всего начать с документации по mockito .

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

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

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

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

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

Джейми Уайтсайд
источник
7

Короче говоря:

@Spyи @Mockшироко используются при тестировании кода, но разработчики действительно путают случаи, когда использовать один из них, и, таким образом, разработчики в конечном итоге используют их в @Mockцелях безопасности.

  • Используйте, @Mockесли вы хотите просто протестировать функциональность извне, не вызывая этот метод.
  • Используйте, @Spyкогда вы хотите протестировать функциональность извне + внутри с помощью самого вызываемого метода.

Ниже приведен пример сценария Election20xx в Америке.

Избирателей можно разделить на VotersOfBelow21и VotersOfABove21.

Идеальный выход опрос говорит , что Trump выиграет выборы , потому что VotersOfBelow21и VotersOfABove21оба будут голосовать за козырем говоря : « Мы выбрали президента Трампа »

Но это не настоящий сценарий:

Избиратели обеих возрастных групп проголосовали за Трампа, потому что у них не было другого эффективного выбора, кроме г-на Трампа.

Так как же это проверить ??

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

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

Теперь ElectionOfYear20XX говорится, что Трамп победил, потому что обе возрастные группы проголосовали за него подавляющим большинством.

Если бы мы протестировали с ElectionOfYear20XXпомощью @Mock, то, возможно, не смогли бы понять истинную причину победы Трампа, мы просто проверим внешнюю причину.

Если мы проверим ElectionOfYear20XX с помощью @Spy, то по результатам внешнего экзит-опроса мы поймем настоящую причину, по которой Трамп победил, то есть внутренне + внешне.


Наши ELectionOfYear20XX_Test класс:

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Это должно выводить только результаты логического теста, т.е. внешнюю проверку:

We elected President Trump 
We elected President Trump 

Тестирование с @Spy внешней, так и с внутренней стороны с фактическим вызовом метода.

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Выход:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 
Вишва Ратна
источник
6

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

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

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

Общее различие:

  • Если вы хотите напрямую заглушить метод (ы) зависимости, то смоделируйте эту зависимость.
  • Если вы хотите заглушить данные в зависимости, чтобы все ее методы возвращали нужные вам тестовые значения, то проследите за этой зависимостью.
leo9r
источник
Обратите внимание, что Spy и Mock всегда предназначены для зависимостей, а не для тестируемой системы.
leo9r