Mockito - @Spy vs @Mock

99

Mockito: entiendo que un espía llama a los métodos reales en un objeto, mientras que un simulacro llama a los métodos en el objeto doble. También se deben evitar los espías a menos que haya un olor a código. Sin embargo, ¿cómo funcionan los espías y cuándo debería utilizarlos? ¿En qué se diferencian de las burlas?

Abhinav
fuente
2
posible duplicado de mockito simulacro vs espía
rds
Posible duplicado de Mocking vs. Spying en
entornos de

Respuestas:

88

Técnicamente hablando, tanto los "burladores" como los "espías" son un tipo especial de "dobles de prueba".

Desafortunadamente, Mockito está haciendo la distinción extraña.

Un simulacro en mockito es un simulacro normal en otros marcos de simulacro (le permite stub invocaciones; es decir, devolver valores específicos de llamadas a métodos).

Un espía en mockito es una burla parcial en otros marcos de burla (parte del objeto será burlado y otra parte usará invocaciones de métodos reales).

Crazyjavahacking
fuente
41

Ambos se pueden utilizar para simular métodos o campos. La diferencia es que en el simulacro, estás creando un objeto simulado o falso completo mientras que en el espía, existe el objeto real y solo estás espiando o detectando métodos específicos del mismo.

Mientras que en los objetos espía, por supuesto, dado que es un método real, cuando no está stubing el método, entonces llamará al comportamiento del método real. Si desea cambiar y simular el método, entonces debe eliminarlo.

Considere el ejemplo siguiente como comparación.

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

¿Cuándo debe usar simulacro o espía? Si desea estar seguro y evitar llamar a servicios externos y solo desea probar la lógica dentro de la unidad, use mock. Si desea llamar a un servicio externo y realizar una llamada de dependencia real, o simplemente decir, desea ejecutar el programa tal como está y solo aplicar métodos específicos, utilice espía. Así que esa es la diferencia entre espiar y simular en mockito.

Vu Truong
fuente
Buena respuesta, pero arrojará verify () en un error solo simulado y no ejecutará las pruebas a menos que inicialice sus listas en el método @Before setUp () como aquí mockList = mock (ArrayList.class); spyList = espía (ArrayList.class); y elimine la anotación simulada y espía sugerida aquí. Lo he probado y mis pruebas están pasando ahora.
The_Martian
17

TL; versión DR,

Con simulacro , crea una instancia de caparazón desnudo para usted.

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

Con espía puede burlarse parcialmente de una instancia existente

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

Caso de uso típico de Spy: la clase tiene un constructor parametrizado, primero desea crear el objeto.

del bao
fuente
14

He creado un ejemplo ejecutable aquí https://www.surasint.com/mockito-with-spy/

Copio algo aquí.

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

Es posible que no necesite un espía porque solo puede simular DepositMoneyService y WithdrawMoneyService.

Pero con algún código heredado, la dependencia está en el código de esta manera:

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

Sí, puede cambiar al primer código, pero luego se cambia la API. Si este método se usa en muchos lugares, debe cambiarlos todos.

La alternativa es que puede extraer la dependencia de esta manera:

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

Entonces puedes usar el espía para inyectar la dependencia de esta manera:

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

Más detalles en el enlace de arriba.

Surasin Tancharoen
fuente
13

El mejor lugar para comenzar son probablemente los documentos de mockito .

En una nota general, el mockito mock te permite crear stubs.

Crearía un método stub si, por ejemplo, ese método realiza una operación costosa. Digamos, obtiene una conexión a la base de datos, recupera un valor de la base de datos y se lo devuelve a la persona que llama. Obtener la conexión de base de datos puede tardar 30 segundos, lo que ralentiza la ejecución de la prueba hasta el punto en el que probablemente cambie de contexto (o deje de ejecutar la prueba).

Si la lógica que está probando no se preocupa por la conexión de la base de datos, entonces puede reemplazar ese método con un código auxiliar que devuelve un valor codificado.

El espía mockito te permite comprobar si un método llama a otros métodos. Esto puede resultar muy útil cuando se intenta probar el código heredado.

Es útil si está probando un método que funciona a través de los efectos secundarios, entonces usaría un espía mockito. Esto delega las llamadas al objeto real y le permite verificar la invocación del método, la cantidad de veces que se invoca, etc.

Jaimie Whiteside
fuente
7

En breve:

@Spyy @Mockse utilizan mucho en las pruebas de código, pero los desarrolladores confunden en los casos en que usar uno de ellos y, por lo tanto, los desarrolladores terminan usándolo @Mockpara estar seguros.

  • Úselo @Mockcuando desee probar la funcionalidad externamente sin llamar a ese método.
  • Úselo @Spycuando desee probar la funcionalidad externamente + internamente con el mismo método al que se llama.

A continuación se muestra el ejemplo en el que he tomado el escenario de Election20xx en Estados Unidos.

Los votantes se pueden dividir según VotersOfBelow21y VotersOfABove21.

La encuesta de salida ideales Trump dice que va a ganar las elecciones porque VotersOfBelow21, y VotersOfABove21ambos van a votar por decir de triunfo " Hemos elegido Presidente de Trump "

Pero este no es el escenario real:

Los votantes de ambos grupos de edad votaron por Trump porque no tenían otra opción efectiva que no fuera Trump.

Entonces, ¿cómo se prueba?

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

}

Ahora tenga en cuenta que en las dos primeras clases anteriores, las personas del grupo de edad dicen que no tienen una mejor opción que Trump. Lo que significa explícitamente que votaron por Trump solo porque no tenían otra opción.

Ahora ElectionOfYear20XX dice que Trump ganó porque ambos grupos de edad votaron por él de manera abrumadora.

Si probamos el ElectionOfYear20XXcon @Mock, entonces es posible que no podamos obtener la verdadera razón por la que Trump ganó, solo probaremos la razón externa.

Si probamos ElectionOfYear20XXcon @Spy, obtenemos la verdadera razón por la que Trump ganó con los resultados de la encuesta de salida externa, es decir, internamente + externamente.


Nuestra ELectionOfYear20XX_Testclase:

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

}

Esto debería generar solo los resultados de la prueba lógica, es decir, la verificación externa:

We elected President Trump 
We elected President Trump 

Pruebas tanto @Spyexternas como internas con la invocación del 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"));
  }

}

Salida:

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 
Vishwa Ratna
fuente
6

Me gusta la sencillez de esta recomendación:

  • Si desea estar seguro y evitar llamar a servicios externos y solo desea probar la lógica dentro de la unidad, use mock .
  • Si desea llamar a un servicio externo y realizar llamadas de dependencias reales, o simplemente decir, desea ejecutar el programa tal como está y simplemente aplicar métodos específicos, utilice espía .

Fuente: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Una diferencia común es:

  • Si desea eliminar directamente los métodos de una dependencia, simule esa dependencia.
  • Si desea guardar los datos en una dependencia para que todos sus métodos devuelvan los valores de prueba que necesita, espíe esa dependencia.
leo9r
fuente
Tenga en cuenta que Spy y Mock siempre están destinados a dependencias, y no al sistema bajo prueba.
leo9r