Mockito: Intentar espiar el método es llamar al método original

351

Estoy usando Mockito 1.9.0. Quiero simular el comportamiento de un solo método de una clase en una prueba JUnit, así que tengo

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

El problema es que, en la segunda línea, en myClassSpy.method1()realidad se llama, lo que resulta en una excepción. La única razón por la que estoy usando simulacros es para que luego, cada vez que myClassSpy.method1()se llame, no se llame al método real y myResultsse devuelva el objeto.

MyClasses una interfaz y myInstancees una implementación de eso, si eso importa.

¿Qué debo hacer para corregir este comportamiento de espionaje?

Dave
fuente
Eche un vistazo a esto: stackoverflow.com/a/29394497/355438
Lu55

Respuestas:

610

Permítanme citar la documentación oficial :

¡Importante truco para espiar objetos reales!

A veces es imposible de usar cuando (Objeto) para tropezar espías. Ejemplo:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

En tu caso es algo así como:

doReturn(resulstIWant).when(myClassSpy).method1();
Tomasz Nurkiewicz
fuente
27
¿Qué sucede si utilizo este método y todavía sigo llamando al original? ¿Podría haber un problema con los parámetros que paso? Aquí está toda la prueba: se llama al método pastebin.com/ZieY790P send
Evgeni Petrov
26
@EvgeniPetrov si su método original todavía se llama, probablemente sea porque su método original es final. Mockito no se burla de los métodos finales, y no puede advertirle sobre la burla de los métodos finales.
MarcG
1
¿esto también es posible para doThrow ()?
Gobliins
1
Sí, desafortunadamente los métodos estáticos son inamovibles y "no pueden espiar". Lo que hago para lidiar con los métodos estáticos es ajustar un método alrededor de la llamada estática y usar doNothing o doReturn en ese método. Con objetos singleton u scala, muevo la carne de la lógica a una clase abstracta y eso me da la posibilidad de tener una clase de prueba alternativa implícita del objeto en el que puedo crear un espía.
Andrew Norman
24
¿Y si todavía no se llama al método NOT final y NOT static?
X-HuMan
27

Mi caso fue diferente de la respuesta aceptada. Estaba tratando de burlarme de un método de paquete privado para una instancia que no vivía en ese paquete

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

y las clases de prueba

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

La compilación es correcta, pero cuando intenta configurar la prueba, invoca el método real.

Declarar el método protegido o público soluciona el problema, aunque no es una solución limpia.

Maragues
fuente
2
Me encontré con un problema similar, pero la prueba y el método privado del paquete estaban en el mismo paquete. Creo que quizás Mockito tiene problemas con los métodos privados de paquetes en general.
Dave
22

En mi caso, usando Mockito 2.0, tuve que cambiar todos los any()parámetros a nullable()para detener la llamada real.

ejaenv
fuente
2
No dejes que esa 321 respuesta superior votada te deprima, esto resolvió mi problema :) ¡He estado luchando con esto durante un par de horas!
Chris Kessel
3
Esta fue la respuesta para mí. Para que sea aún más fácil para los que siguen al burlarse de su método, la sintaxis es: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder
Con Mockito 2.23.4 puedo confirmar que esto no es necesario, funciona bien con anyy eqcoincidencias.
vmaldosan
2
Probé tres enfoques diferentes en la versión 2.23.4 lib: any (), eq () y nullable (). Solo el último funcionó
ryzhman
Hola, tu solución fue muy buena y también funcionó para mí. Gracias
Dhiren Solanki
16

¡La respuesta de Tomasz Nurkiewicz parece no contar toda la historia!

NB Versión Mockito: 1.10.19.

Soy un Mockito newb, así que no puedo explicar el siguiente comportamiento: si hay un experto por ahí que pueda mejorar esta respuesta, por favor siéntase libre.

El método en cuestión aquí, NOgetContentStringValue es y NO . final static

Esta línea hace llamar el método original getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

Esta línea no llama al método original getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

Por razones que no puedo responder, el uso isA()hace doReturnque falle el comportamiento pretendido (?) De "no llamar al método" .

Veamos las firmas de métodos involucradas aquí: ambos son staticmétodos de Matchers. El Javadoc dice que ambos regresan null, lo cual es un poco difícil de entender. Presumiblemente, el Classobjeto pasado mientras se examina el parámetro, pero el resultado nunca se calculó ni se descartó. Dado que nullpuede representar cualquier clase y que espera que no se invoque el método simulado, ¿no podrían las firmas isA( ... )y any( ... )simplemente regresar en nulllugar de un parámetro genérico * <T>?

De todas formas:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

La documentación de la API no da ninguna pista sobre esto. También parece decir que la necesidad de tal comportamiento de "no llamar método" es "muy rara". Personalmente, uso esta técnica todo el tiempo : normalmente encuentro que las burlas implican unas pocas líneas que "preparan la escena" ... seguido de un método que luego "reproduce" la escena en el contexto simulado que has puesto en escena ... .y mientras está configurando el escenario y los accesorios, lo último que desea es que los actores entren al escenario a la izquierda y comiencen a actuar con el corazón ...

Pero esto está más allá de mi calificación salarial ... Invito a explicaciones de cualquier sumo sacerdote Mockito que pase ...

* ¿es "parámetro genérico" el término correcto?

Mike roedor
fuente
No sé si esto agrega claridad o confunde aún más el asunto, pero la diferencia entre isA () y any () es que isA realmente realiza la verificación de tipos, mientras que cualquier familia de métodos () se creó simplemente para evitar la conversión de tipos de argumento.
Kevin Welker
@KevinWelker Gracias. Y, de hecho, los nombres de los métodos no carecen de una cierta calidad que se explica por sí misma. Sin embargo, y con moderación, discrepo con los geniales diseñadores de Mockito por no documentar adecuadamente. Sin duda necesito leer otro libro sobre Mockito. PD: ¡en realidad parece haber muy pocos recursos para enseñar "Mockito intermedio"!
Mike roedor
1
La historia es que los métodos anyXX se crearon primero como una forma de lidiar solo con la conversión de texto. Luego, cuando se sugirió que agregaran la comprobación de argumentos, no querían romper los usuarios de la API existente, por lo que crearon la familia isA (). Sabiendo que los métodos any () deberían haber realizado el tipo de verificación todo el tiempo, retrasaron el cambio hasta que introdujeron otros cambios importantes en la revisión de Mockito 2.X (que aún no he probado). En 2.x +, los métodos anyX () son alias para los métodos isA ().
Kevin Welker
Gracias. Esta es una respuesta fundamental para aquellos de nosotros que hacemos varias actualizaciones de la biblioteca al mismo tiempo, porque el código que solía ejecutarse falla repentina y silenciosamente.
Dex Stakker
6

Otro escenario posible que puede causar problemas con los espías es cuando está probando frijoles de primavera (con el marco de prueba de primavera) o algún otro marco que está proxing sus objetos durante la prueba .

Ejemplo

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

En el código anterior, Spring y Mockito intentarán representar su objeto MonitoringDocumentsRepository, pero Spring será el primero, lo que provocará una llamada real del método findMonitoringDocuments. Si depuramos nuestro código justo después de poner un espía en el objeto del repositorio, se verá así dentro del depurador:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean al rescate

Si, en cambio @Autowired, usamos la @SpyBeananotación, resolveremos el problema anterior, la anotación SpyBean también inyectará un objeto de repositorio, pero Mockito lo representará primero y se verá así dentro del depurador

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

y aquí está el código:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}
Adrian Kapuscinski
fuente
1

He encontrado otra razón para que el espía llame al método original.

Alguien tuvo la idea de burlarse de una finalclase y descubrió lo siguiente MockMaker:

Como esto funciona de manera diferente a nuestro mecanismo actual y este tiene limitaciones diferentes y deseamos reunir experiencia y comentarios de los usuarios, esta característica tuvo que activarse explícitamente para estar disponible; Se puede hacer a través del mecanismo de extensión mockito creando el archivo que src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontiene una sola línea:mock-maker-inline

Fuente: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Después de fusionarme y traer ese archivo a mi máquina, mis pruebas fallaron.

Simplemente tuve que eliminar la línea (o el archivo) y spy()trabajé.

Matruskan
fuente
Esta fue la razón en mi caso, estaba tratando de burlarme de un método final pero seguía llamando al real sin un mensaje de error claro que era confuso.
Bashar Ali Labadi
1

Llegué un poco tarde a la fiesta, pero las soluciones anteriores no me funcionaron, así que compartir mi 0.02

Versión de Mokcito: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

Lo siguiente NO funcionó para mí (se estaba llamando al método real):

1)

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2)

doReturn(0).when(spy , "handleAction", any(), anyString());

3)

doReturn(0).when(spy , "handleAction", null, null);

Siguiendo TRABAJADO:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());
tratando de aprender
fuente
0

Una forma de asegurarse de que no se llame a un método de una clase es anular el método con un ficticio.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });
Geoffrey Ritchey
fuente
-1

Respuesta para usuarios de scala: ¡Incluso poner doReturnprimero no funciona! Ver este post .

Nick Resnick
fuente
esto no es una respuesta
Umpa