¿Cuál es la diferencia entre burlarse y espiar cuando se usa Mockito?

137

¿Cuál sería un caso de uso para un espía Mockito?

Me parece que cada caso de uso de espías se puede manejar con una simulación, usando callRealMethod.

Una diferencia que puedo ver es que si desea que la mayoría de las llamadas a métodos sean reales, guarda algunas líneas de código para usar un simulacro frente a un espía. ¿Es eso o me estoy perdiendo el panorama general?

Victor Grazi
fuente

Respuestas:

100

La respuesta está en la documentación :

Simulacros parciales reales (desde 1.8.0)

Finalmente, después de muchos debates internos y discusiones sobre la lista de correo, se agregó un apoyo simulado parcial a Mockito. Anteriormente consideramos simulacros parciales como olores de código. Sin embargo, encontramos un caso de uso legítimo para simulacros parciales.

Antes del lanzamiento 1.8, spy () no producía simulacros parciales reales y era confuso para algunos usuarios. Lea más sobre espionaje: aquí o en javadoc para el método espía (Objeto).

callRealMethod()se introdujo después spy(), pero spy () se dejó allí, por supuesto, para garantizar la compatibilidad con versiones anteriores.

De lo contrario, tienes razón: todos los métodos de un espía son reales a menos que se tropecen. Todos los métodos de un simulacro se topan a menos que callRealMethod()se llame. En general, preferiría usar callRealMethod(), porque no me obliga a usar el doXxx().when()idioma en lugar del tradicionalwhen().thenXxx()

JB Nizet
fuente
El problema con preferir el simulacro en lugar de espía en estos casos es cuando la clase usa un miembro que no se inyecta en él (sino que se inicializa localmente), y luego se usa por el método "real"; en el simulacro, el miembro se inicializará a su valor predeterminado de Java, lo que podría causar un comportamiento incorrecto o incluso una NullPointerException. La forma de pasar esto es agregar un método "init" y luego "realmente" llamarlo, pero eso me parece un poco exagerado.
Eyal Roth
Del documento: "los espías deben usarse con cuidado y ocasionalmente, por ejemplo, cuando se trata de código heredado". El espacio de prueba de la unidad sufre de demasiadas formas de hacer lo mismo.
gdbj
89

Diferencia entre un espía y un simulacro

Cuando Mockito crea un simulacro, lo hace desde la Clase de un Tipo, no desde una instancia real. El simulacro simplemente crea una instancia de shell básica de la Clase, completamente instrumentada para rastrear las interacciones con ella. Por otro lado, el espía envolverá una instancia existente. Todavía se comportará de la misma manera que la instancia normal: la única diferencia es que también estará instrumentado para rastrear todas las interacciones con él.

En el siguiente ejemplo, creamos un simulacro de la clase ArrayList:

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

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

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

Como puede ver, agregar un elemento a la lista simulada en realidad no agrega nada, solo llama al método sin ningún otro efecto secundario. Un espía, por otro lado, se comportará de manera diferente: en realidad llamará a la implementación real del método add y agregará el elemento a la lista subyacente:

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

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

Aquí seguramente podemos decir que se llamó al método interno real del objeto porque cuando llamas al método size () obtienes el tamaño como 1, ¡pero este método size () no se ha burlado! Entonces, ¿de dónde viene 1? El método interno de tamaño real () se llama ya que el tamaño () no se burla (ni se corta) y, por lo tanto, podemos decir que la entrada se agregó al objeto real.

Fuente: http://www.baeldung.com/mockito-spy + self notes.

Saurabh Patil
fuente
1
¿No quieres decir que size () devuelve 1?
negro
En el primer ejemplo, ¿por qué mockedList.size()regresa 0si ese método tampoco se ha eliminado? ¿Es solo un valor predeterminado dado el tipo de retorno del método?
Mike
@mike: mockedList.size()devuelve un intvalor predeterminado de int0 en Java. Si intenta ejecutar assertEquals(0, mockedList.size());después mockedList.clear();, el resultado permanece igual.
realPK
2
Esta respuesta está bien y simplemente escrita y me ayudó a comprender finalmente la diferencia entre simulacro y espía. Buena esa.
Pesa
38

Si hay un objeto con 8 métodos y tiene una prueba en la que desea llamar a 7 métodos reales y un método único, tiene dos opciones:

  1. Usando un simulacro, tendría que configurarlo invocando 7 callRealMethod y stub one method
  2. Usando un spytienes que configurarlo al aplicar un método

La documentación oficial sobre doCallRealMethodrecomienda el uso de un espía para simulacros parciales.

Consulte también javadoc spy (Object) para obtener más información sobre simulacros parciales. Mockito.spy () es una forma recomendada de crear simulacros parciales. La razón es que garantiza que los métodos reales se invocan contra el objeto construido correctamente porque usted es responsable de construir el objeto pasado al método spy ().

usuario2412398
fuente
5

Spy puede ser útil cuando desea crear pruebas unitarias para código heredado .

He creado un ejemplo ejecutable aquí https://www.surasint.com/mockito-with-spy/ , copio algunos de ellos aquí.

Si tienes 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 espía porque simplemente puede burlarse de DepositMoneyService y WithdrawMoneyService.

Pero con algunos códigos heredados, 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 está siendo utilizado por 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();
    }

Luego puede 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
0

MockEs un doble objeto desnudo. Este objeto tiene las mismas firmas de métodos, pero la realización está vacía y devuelve el valor predeterminado: 0 y nulo

SpyEs un objeto doble clonado. El nuevo objeto se clona en función de un objeto real, pero tiene la posibilidad de burlarse de él

class A {

    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() {
        foo4();
    }

    void foo4() {

    }
}
@Test
public void testMockA() {

    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

[Prueba de tipos dobles]

yoAlex5
fuente