Mockito - Eu entendo que um espião chama os métodos reais em um objeto, enquanto um mock chama métodos no objeto duplo. Além disso, espiões devem ser evitados, a menos que haja um cheiro de código. No entanto, como funcionam os espiões e quando devo realmente usá-los? Como eles são diferentes dos mocks?
103
Respostas:
Tecnicamente falando, tanto "mocks" quanto "espiões" são um tipo especial de "duplas de teste".
Mockito infelizmente está tornando a distinção estranha.
Um mock no mockito é um mock normal em outras estruturas de mocking (permite que você crie invocações; isto é, retornar valores específicos de chamadas de método).
Um espião no mockito é um mock parcial em outros frameworks de mocking (parte do objeto será simulado e parte usará invocações de métodos reais).
fonte
Ambos podem ser usados para simular métodos ou campos. A diferença é que no mock, você está criando um mock ou um objeto falso completo, enquanto no spy, existe o objeto real e você apenas espia ou faz stub em métodos específicos dele.
Enquanto em objetos espiões, é claro, uma vez que é um método real, quando você não está fazendo o stub do método, ele chamará o comportamento do método real. Se você quiser alterar e simular o método, precisará fazer um stub.
Considere o exemplo abaixo como uma comparação.
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)); } }
Quando você deve usar mock ou spy? Se você quiser ficar seguro e evitar chamar serviços externos e só quiser testar a lógica dentro da unidade, use mock. Se você deseja chamar um serviço externo e executar uma chamada de dependência real, ou simplesmente dizer que deseja executar o programa como está e apenas criar um esboço de métodos específicos, use o spy. Então essa é a diferença entre spy e mock no mockito.
fonte
TL; versão DR,
Com o mock , ele cria uma instância de shell óssea para você.
Com o spy você pode simular parcialmente em uma instância existente
List<String> spyList = Mockito.spy(new ArrayList<String>());
Caso de uso típico para Spy: a classe tem um construtor parametrizado, você deseja criar o objeto primeiro.
fonte
Eu criei um exemplo executável aqui https://www.surasint.com/mockito-with-spy/
Eu copio algumas delas aqui.
Se você tiver algo como este código:
public void transfer( DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, double amount, String fromAccount, String toAccount) { withdrawMoneyService.withdraw(fromAccount,amount); depositMoneyService.deposit(toAccount,amount); }
Você pode não precisar do espião porque pode apenas simular DepositMoneyService e WithdrawMoneyService.
Mas com algum código legado, a dependência está no código assim:
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); }
Sim, você pode mudar para o primeiro código, mas a API é alterada. Se este método está sendo usado por muitos lugares, você deve alterar todos eles.
A alternativa é que você pode extrair a dependência desta forma:
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(); }
Então você pode usar o espião para injetar a dependência assim:
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();
Mais detalhes no link acima.
fonte
O melhor lugar para começar é provavelmente a documentação do mockito .
Em uma nota geral, o mockito mock permite que você crie stubs.
Você criaria um método stub se, por exemplo, esse método fizer uma operação cara. Digamos que ele obtém uma conexão com o banco de dados, recupera um valor do banco de dados e o retorna para o chamador. Obter a conexão do banco de dados pode levar 30 segundos, retardando a execução do teste até o ponto em que você provavelmente mudará o contexto (ou parará de executar o teste).
Se a lógica que você está testando não se importa com a conexão do banco de dados, você pode substituir esse método por um esboço que retorna um valor codificado.
O mockito spy permite que você verifique se um método chama outros métodos. Isso pode ser muito útil ao tentar colocar o código legado em teste.
É útil se você estiver testando um método que funciona por meio de efeitos colaterais, então você usaria um espião mockito. Isso delega chamadas para o objeto real e permite que você verifique a invocação do método, o número de vezes que é invocado etc.
fonte
Em resumo:
@Spy
e@Mock
são muito usados em testes de código, mas os desenvolvedores confundem os casos de quando usar um deles e, portanto, os desenvolvedores acabam usando@Mock
para estarem seguros.@Mock
quando quiser apenas testar a funcionalidade externamente, sem realmente chamar esse método.@Spy
quando quiser testar a funcionalidade externa + internamente com o próprio método que está sendo chamado.Abaixo está o exemplo de onde tirei o cenário de Election20xx na América.
Os eleitores podem ser divididos de acordo com
VotersOfBelow21
eVotersOfABove21
.A pesquisa de saída ideal diz que Trump vai ganhar a eleição porque
VotersOfBelow21
eVotersOfABove21
ambos vão votar em trunfo dizendo " Nós elegemos o presidente Trump "Mas este não é o cenário real:
Então, como você testa ??
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; } }
Agora observe nas duas primeiras classes acima, ambas as pessoas da faixa etária dizem que não têm escolha melhor do que trunfo. O que significa explicitamente que votaram em Trump apenas porque não tinham escolha.
Agora, o
ElectionOfYear20XX
diz que Trump ganhou porque ambas as faixas etárias votaram nele de forma esmagadora.Se fôssemos testar o
ElectionOfYear20XX
com @Mock, talvez não consigamos descobrir a verdadeira razão pela qual Trump venceu, estaremos apenas testando a razão externa.Se testarmos
ElectionOfYear20XX
com @Spy, obteremos a verdadeira razão pela qual Trump ganhou com os resultados da pesquisa de saída externa, ou seja, internamente + externamente.Nossa
ELectionOfYear20XX_Test
classe:@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")); } }
Isso deve produzir apenas os resultados do teste lógico, ou seja, verificação externa:
Testando
@Spy
externamente e internamente com invocação de método real.@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")); } }
Resultado:
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
fonte
Gosto da simplicidade desta recomendação:
Fonte: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
Uma diferença comum é:
fonte