Cómo burlarse de los métodos nulos con Mockito

938

¿Cómo burlarse de los métodos con tipo de retorno nulo?

Implementé un patrón de observación, pero no puedo burlarme de él porque no sé cómo.

Y traté de encontrar un ejemplo en Internet pero no tuve éxito.

Mi clase se ve así:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

El sistema no se activa con simulacro.

Quiero mostrar el estado del sistema mencionado anteriormente. Y hacer afirmaciones según ellos.

ibrahimyilmaz
fuente
66
¡Tenga en cuenta que los métodos nulos en simulacros no hacen nada por defecto!
Línea
1
@Line, eso es lo que estaba buscando. Parece obvio después de que lo dices. Pero resalta un principio burlón: solo necesita burlarse de los métodos de las clases burladas para sus efectos, como un valor de retorno o una excepción. ¡Gracias!
allenjom

Respuestas:

1145

Echa un vistazo a los documentos de la API de Mockito . A medida que el documento vinculado menciona (punto # 12), puede utilizar cualquiera de los doThrow(), doAnswer(), doNothing(), doReturn()familia de métodos de marco Mockito para burlarse de los métodos vacíos.

Por ejemplo,

Mockito.doThrow(new Exception()).when(instance).methodName();

o si quieres combinarlo con un comportamiento de seguimiento,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

Suponiendo que está buscando burlarse del setter setState(String s)en la clase World a continuación, el código usa el doAnswermétodo para burlarse del setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());
sateesh
fuente
8
@qualidafial: Sí, creo que la parametrización a Void sería mejor, ya que transmite mejor que no estoy interesado en el tipo de devolución. No estaba al tanto de esta construcción, gracias por señalarlo.
sateesh
2
doThrow es el # 5 ahora (también para mí usando doThrow esto solucionó el mensaje "tipo 'vacío' no permitido aquí", para seguidores ...)
rogerdpack
@qualidafial: creo que el tipo de retorno de la llamada Answer.answer no es lo que se devuelve al método original, es lo que se devuelve a la llamada doAnswer, presumiblemente si desea hacer algo más con ese valor en su prueba.
doce y
1
:( al intentar simular la versión 16.0.1 de RateLimiter.java en guava doNothing (). when (mockLimiterReject) .setRate (100) da como resultado que se llame al setRate del RateLimiter resultando en un puntero nulo ya que mutex es nulo por alguna razón una vez que el mockito está codificado así que no se burló de mi método setRate :( sino que lo llamó :(
Dean Hiller
2
@DeanHiller nota que setRate()es final, y por lo tanto no se puede burlar. En su lugar, intente probar create()una instancia que haga lo que necesita. No debería haber necesidad de burlarse RateLimiter.
dimo414
113

Creo que he encontrado una respuesta más simple a esa pregunta, para llamar al método real para un solo método (incluso si tiene un retorno nulo), puede hacer esto:

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

O bien, puede llamar al método real para todos los métodos de esa clase, haciendo esto:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
MarcioB
fuente
13
Esta es la verdadera respuesta aquí mismo. El método spy () funciona bien, pero generalmente está reservado para cuando desea que el objeto haga casi todo normalmente.
biggusjimmus
1
¿Qué significa esto? ¿Realmente estás llamando a los métodos? Realmente no he usado mockito antes.
obesechicken13
Sí, el simulacro llamará a los métodos reales. Si usa @Mock, puede especificar lo mismo con: @Mock (answer = Answers.CALLS_REAL_METHODS) para obtener los mismos resultados.
Ale
70

Además de lo que dijo @sateesh, cuando solo quieres burlarte de un método nulo para evitar que la prueba lo llame, puedes usar un Spymétodo de esta manera:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Cuando desee ejecutar su prueba, asegúrese de llamar al método en prueba en el spyobjeto y no en el worldobjeto. Por ejemplo:

assertEquals(0,spy.methodToTestThatShouldReturnZero());
Omri374
fuente
57

La solución del llamado problema es usar un spy Mockito.spy (...) en lugar de un mock Mockito.mock (..) .

Spy nos permite burlas parciales. Mockito es bueno en este asunto. Debido a que tiene una clase que no está completa, de esta manera se burla de algún lugar requerido en esta clase.

ibrahimyilmaz
fuente
3
Tropecé aquí porque tenía un problema similar (también, casualmente, estaba probando una interacción sujeto / observador). Ya estoy usando un espía, pero quiero que el método 'SubjectChanged' haga algo diferente. Podría usar `verificar (observador) .subjectChanged (asunto) solo para ver que se llamó al método. Pero, por alguna razón, prefiero anular el método. Para ello, una combinación del enfoque de Sateesh y su respuesta aquí era el camino a seguir ...
gMale
32
No, hacer esto en realidad no ayudará con los métodos de anulación burlona. El truco consiste en utilizar uno de los cuatro métodos estáticos de Mockito que figuran en la respuesta de sateesh.
Dawood ibn Kareem
2
@Gurnard para su pregunta, eche un vistazo a este stackoverflow.com/questions/1087339/… .
ibrahimyilmaz
Este dosificador realmente funciona.
Nilotpal
34

En primer lugar: siempre debe importar mockito static, de esta manera el código será mucho más legible (e intuitivo):

import static org.mockito.Mockito.*;

Para burlarse parcial y aún mantener la funcionalidad original en el resto, mockito ofrece "Spy".

Puede usarlo de la siguiente manera:

private World world = spy(World.class);

Para eliminar la ejecución de un método, puede usar algo como esto:

doNothing().when(someObject).someMethod(anyObject());

para dar un comportamiento personalizado a un método, use "when" con un "thenReturn":

doReturn("something").when(this.world).someMethod(anyObject());

Para obtener más ejemplos, encuentre las excelentes muestras de mockito en el documento.

fl0w
fuente
44
¿Qué tiene que ver la importación estática para que sea más legible?
jsonbourne
2
literalmente nada.
specializt
2
Creo que es cuestión de gustos, solo me gusta que la declaración se vea (casi) como una oración en inglés, y Class.methodname (). Something () vs. methodname () .algo es menos legible de forma fluida.
fl0w
1
Si trabaja en un equipo, la importación estática hace que sea muy difícil para cualquier otra persona ver de dónde viene el método, ya que a menudo hay un comodín en la importación.
LowKeyEnergy
eso es correcto, sin embargo, para el código de prueba y Mockito, esto debería ser obvio y no un problema.
fl0w
27

Cómo burlarse de los métodos nulos con mockito: hay dos opciones:

  1. doAnswer - Si queremos que nuestro método vacío simulado haga algo (burlarse del comportamiento a pesar de estar vacío).
  2. doThrow- Luego está Mockito.doThrow()si quieres lanzar una excepción del método de anulación simulada.

El siguiente es un ejemplo de cómo usarlo (no es un caso de uso ideal pero solo quería ilustrar el uso básico).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("[email protected]")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("[email protected]", "[email protected]");

}
}

Puede encontrar más detalles sobre cómo burlarse y probar métodos vacíos con Mockito en mi publicación Cómo burlarse de Mockito (Una guía completa con ejemplos)

Dilini Rajapaksha
fuente
1
Gran ejemplo Nota: En Java 8, podría ser un poco mejor usar una lambda en lugar de una clase anónima: 'doAnswer ((Answer <Void>) invocación -> {// CODE}). When (mockInstance) .add (método ()); '
miwe
16

Agregar otra respuesta al grupo (sin juego de palabras) ...

Debe llamar al método doAnswer si no puede \ no quiere usar espías. Sin embargo, no necesariamente necesita lanzar su propia respuesta . Hay varias implementaciones predeterminadas. En particular, CallsRealMethods .

En la práctica, se parece a esto:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

O:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));
javamonkey79
fuente
16

En Java 8, esto se puede hacer un poco más limpio, suponiendo que tenga una importación estática para org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

Esto return null;es importante y sin él la compilación fallará con algunos errores bastante oscuros, ya que no podrá encontrar una anulación adecuada paradoAnswer .

Por ejemplo, un ExecutorServiceque solo ejecuta inmediatamente cualquier Runnablepase que se execute()pueda implementar usando:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());
Tim B
fuente
2
En una línea: Mockito.doAnswer ((i) -> null) .when (instancia) .method (any ());
Akshay Thorve
@AkshayThorve Sin embargo, eso no funciona cuando realmente quieres hacer cosas con i.
Tim B
7

Creo que sus problemas se deben a su estructura de prueba. Me resulta difícil mezclar burlas con el método tradicional de implementación de interfaces en la clase de prueba (como lo ha hecho aquí).

Si implementa el oyente como un simulacro, puede verificar la interacción.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Esto debería satisfacerte de que el 'Mundo' está haciendo lo correcto.

ashley
fuente
0

Usando Mockito.doThrow como en:

Mockito.doThrow(new Exception()).when(instance).methodName();

puedes probar este buen ejemplo:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Fuente: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

APISonar
fuente