Mockito: Tratar de espiar el método es llamar al método original


Estoy usando Mockito 1.9.0. Quiero burlarme del 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, myClassSpy.method1() está siendo llamado, lo que resulta en una excepción. La única razón por la que estoy usando mocks es para que más tarde, cada vez que se llame a myClassSpy.method1(), no se llame al método real y se devuelva el objeto myResults.

MyClass es una interfaz y myInstance es una implementación de eso, si eso importa.

¿Qué hago necesidad de hacer para corregir este comportamiento de espionaje?

Author: adam_0, 2012-07-24

7 answers

Permítanme citar la documentación oficial:

Importante gotcha en el espionaje de objetos reales!

A veces es imposible de usar cuando(Objeto) para espías stubbing. 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 su caso va algo como:

doReturn(resulstIWant).when(myClassSpy).method1();
 420
Author: Tomasz Nurkiewicz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-20 04:14:22

Mi caso fue diferente de la respuesta aceptada. Estaba tratando de burlarme de un método package-private 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 en su lugar.

Declarar el método protectedo public soluciona el problema, aunque no es una solución limpia.

 21
Author: Maragues,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-29 08:17:57

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

NB Mockito versión: 1.10.19.

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

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

Esta línea hace llamar al 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, usar isA() causa la intención (?) comportamiento" no llamar al método " de doReturn para fallar.

Echemos un vistazo a las firmas del método involucradas aquí: ambos son static métodos de Matchers. Ambos se dice por el Javadoc para volver null, que es un poco difícil de conseguir su cabeza alrededor en sí mismo. Presumiblemente el objeto Class pasado como parámetro es examinado pero el resultado nunca calculado o descartado. Dado que null puede representar cualquier clase y que espera que no se llame al método burlado, ¿no podrían las firmas de isA( ... ) y any( ... ) simplemente devolver null en lugar de un parámetro genérico* <T>?

De todos modos:

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 al método "es"muy rara". Personalmente uso esta técnica todo el tiempo : normalmente me encuentro esa burla implica unas pocas líneas que "establecen la escena"... seguido de llamar a un método que luego "reproduce" la escena en el contexto simulado que ha escenificado... y mientras preparas el escenario y los accesorios lo último que quieres es que los actores entren a la izquierda del escenario y empiecen a actuar con el corazón...

Pero esto está mucho más allá de mi nivel salarial... Invito explicaciones de cualquier alto sacerdote Mockito que pase...

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

 13
Author: mike rodent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-01 22:07:58

En mi caso, usando Mockito 2.0, tuve que cambiar todos los parámetros any() a nullable() con el fin de stub la llamada real.

 10
Author: ejaenv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-29 20:46:24

He encontrado otra razón para que spy llame al método original.

Alguien tuvo la idea de burlarse de una clase final, y encontró alrededor de MockMaker:

Como esto funciona de manera diferente a nuestro mecanismo actual y este tiene diferentes limitaciones y como queremos recopilar 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 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker que contiene 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 fusionar y traer ese archivo a mi máquina, mis pruebas fallaron.

Solo tuve que eliminar la línea (o el archivo), y spy() funcionó.

 0
Author: Matruskan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-17 11:57:27

Una forma de asegurarse de que un método de una clase no es llamado es sobrescribir el método con un dummy.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });
 0
Author: Geoffrey Ritchey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-19 21:28:36

Otro escenario posible que puede causar problemas con spies es cuando está probando spring beans (con spring test framework) o algún otro framework que está procesando 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, tanto Spring como Mockito intentarán proxy de su objeto MonitoringDocumentsRepository, pero Spring será el primero, lo que causará una llamada real del método findMonitoringDocuments. Si depuramos nuestro código justo después de poner un espía en objeto repositorio se verá así dentro del depurador:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean al rescate

Si en su lugar @Autowired anotación usamos @SpyBean anotación, resolveremos el problema anterior, la anotación SpyBean también inyectará el objeto repository pero primero será proxy por Mockito 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));
}
 0
Author: Adrian Kapuscinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-10-08 14:10:11