java - examples - mockito verify
¿Cómo funcionan los emparejadores Mockito? (2)
Los conciliadores de argumentos de Mockito (como any
, argThat
, eq
, same
y ArgumentCaptor.capture()
) se comportan de forma muy diferente a los matchers de Hamcrest.
Los mezcladores de Mockito frecuentemente causan InvalidUseOfMatchersException, incluso en el código que se ejecuta mucho después de que se usaron los emparejamientos.
Los matchers de Mockito están sujetos a reglas extrañas, como solo requerir el uso de emparejamientos de Mockito para todos los argumentos si un argumento en un método dado usa un matcher.
Los mezcladores de Mockito pueden causar NullPointerException al anular
Answer
s o al usar(Integer) any()
etc.El código de refactorización con los adaptadores de Mockito de ciertas maneras puede producir excepciones y comportamientos inesperados, y puede fallar por completo.
¿Por qué se diseñan los adaptadores de Mockito de esta manera y cómo se implementan?
Solo una pequeña adición a la excelente respuesta de Jeff Bowman, ya que encontré esta pregunta al buscar una solución a uno de mis problemas:
Si una llamada a un método coincide con más de un simulacro de llamadas entrenadas, el orden de las llamadas al when
es importante, y debe ser desde el más amplio hasta el más específico. A partir de uno de los ejemplos de Jeff:
when(foo.quux(anyInt(), anyInt())).thenReturn(true);
when(foo.quux(anyInt(), eq(5))).thenReturn(false);
es el orden que garantiza el resultado (probablemente) deseado:
foo.quux(3 /*any int*/, 8 /*any other int than 5*/) //returns true
foo.quux(2 /*any int*/, 5) //returns false
Si invierte las llamadas al momento, el resultado siempre será true
.
Los mezcladores de Mockito son métodos estáticos y llamadas a esos métodos, que sustituyen a los argumentos durante las llamadas a when
y verify
.
Los emparejadores de Hamcrest (versión archivada) (o los mezcladores de estilo Hamcrest) son instancias de objetos de propósito general sin estado que implementan Matcher<T>
y exponen un método que matches(T)
que devuelve verdadero si el objeto coincide con los criterios del Matcher. Están destinados a ser libres de efectos secundarios, y generalmente se utilizan en afirmaciones como la que se muestra a continuación.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Existen combinaciones de Mockito, separadas de las parejas de estilo Hamcrest, de modo que las descripciones de las expresiones coincidentes se ajustan directamente a las invocaciones de métodos : las parejas de Mockito devuelven T
donde los métodos de Matcher Hamrest devuelven objetos de Matcher (de tipo Matcher<T>
).
Las combinaciones de Mockito se invocan mediante métodos estáticos como eq
, any
, gt
y startsWith
en org.mockito.Matchers
y org.mockito.AdditionalMatchers
. También hay adaptadores, que han cambiado en las versiones de Mockito:
- Para Mockito 1.x,
Matchers
presentó algunas llamadas (comointThat
oargThat
) son las que coinciden con Mockito que aceptan directamente los parámetros de Hamcrest matchers.ArgumentMatcher<T>
extendedorg.hamcrest.Matcher<T>
, que se usó en la representación interna de Hamcrest y fue una clase base de Hamcrest matcher en lugar de cualquier clase de matcher Mockito. - Para Mockito 2.0+, Mockito ya no tiene una dependencia directa de Hamcrest.
Matchers
llamadas deMatchers
se expresan comointThat
oargThat
envuelven aArgumentMatcher<T>
objetos que ya no implementanorg.hamcrest.Matcher<T>
pero que se usan de forma similar. Los adaptadores Hamcrest comoargThat
eintThat
siguen disponibles, pero se han movido aMockitoHamcrest
.
Independientemente de si los mezcladores son Hamcrest o simplemente estilo Hamcrest, se pueden adaptar de la siguiente manera:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
En la declaración anterior: foo.setPowerLevel
es un método que acepta un int
. is(greaterThan(9000))
devuelve un Matcher<Integer>
, que no funcionaría como un argumento setPowerLevel
. El matcher Mockito intThat
envuelve ese Matcher de estilo Hamcrest y devuelve un int
para que pueda aparecer como un argumento; Los mezcladores de Mockito como gt(9000)
envolverían toda esa expresión en una sola llamada, como en la primera línea del código de ejemplo.
Qué hacen / devuelven los emparejadores
when(foo.quux(3, 5)).thenReturn(true);
Cuando no usa argumentos de argumento, Mockito registra los valores de su argumento y los compara con sus métodos equals
.
when(foo.quux(eq(3), eq(5))).thenReturn(true); // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one''s different
Cuando llamas a un matcher como any
o gt
(mayor que), Mockito almacena un objeto de coincidencia que hace que Mockito omita esa comprobación de igualdad y aplique la coincidencia de tu elección. En el caso de argumentCaptor.capture()
almacena una coincidencia que guarda su argumento en su lugar para una inspección posterior.
Los devolvedores devuelven valores ficticios como cero, colecciones vacías o null
. Mockito intenta devolver un valor ficticio seguro y apropiado, como 0 para anyInt()
o any(Integer.class)
o una List<String>
vacía List<String>
para anyListOf(String.class)
. Sin embargo, debido al borrado de tipo, Mockito carece de información de tipo para devolver cualquier valor, pero null
para any()
o argThat(...)
, lo que puede causar una NullPointerException si intenta "auto-unbox" un null
primitivo null
.
Los participantes como eq
y gt
toman valores de parámetros; idealmente, estos valores se deben calcular antes de que comience la comprobación / comprobación. Llamar a un simulacro en medio de burlarse de otra llamada puede interferir con el punteo.
Los métodos de Matcher no se pueden usar como valores de retorno; no hay forma de expresar thenReturn(anyInt())
o thenReturn(any(Foo.class))
en Mockito, por ejemplo. Mockito necesita saber exactamente qué instancia devolver en las llamadas de interpolación, y no elegirá un valor de retorno arbitrario para usted.
Detalles de implementacion
Los Matchers se almacenan (como equilibradores de objetos de estilo Hamcrest) en una pila contenida en una clase llamada ArgumentMatcherStorage . MockitoCore y Matchers poseen cada uno una instancia de ThreadSafeMockingProgress , que contiene de forma estática un ThreadLocal con instancias de MockingProgress. Es este MockingProgressImpl que contiene un ArgumentMatcherStorageImpl concreto. En consecuencia, el estado de simulacro y el de emparejamiento es estático, pero tiene un ámbito de subprocesos consistente entre las clases Mockito y Matchers.
La mayoría de las llamadas de emparejamiento solo agregan a esta pila, con una excepción para los evaluadores como and
, or
, y not
. Esto se corresponde perfectamente (y depende de) el orden de evaluación de Java , que evalúa los argumentos de izquierda a derecha antes de invocar un método:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
Esta voluntad:
- Agregue
anyInt()
a la pila. - Agrega
gt(10)
a la pila. - Agrega
lt(20)
a la pila. - Elimina
gt(10)
ylt(20)
y sumaand(gt(10), lt(20))
. - Llame a
foo.quux(0, 0)
, que (a menos que se repita) devuelve el valor predeterminadofalse
. Internamente Mockito marcaquux(int, int)
como la llamada más reciente. - Call
when(false)
, que descarta su argumento y se prepara para stub methodquux(int, int)
identificado en 5. Los únicos dos estados válidos son con stack length 0 (igualdad) o 2 (matchers), y hay dos matchers en la pila (pasos 1 y 4), por lo que Mockito corta el método con un matcherany()
para su primer argumento yyand(gt(10), lt(20))
para su segundo argumento y borra la pila.
Esto demuestra algunas reglas:
Mockito no puede distinguir entre
quux(anyInt(), 0)
yquux(0, anyInt())
. Ambos parecen una llamada aquux(0, 0)
con un int matcher en la pila. En consecuencia, si usa un emparejador, debe hacer coincidir todos los argumentos.El orden de las llamadas no es solo importante, es lo que hace que todo funcione . Por lo general, la extracción de emparejamientos de variables no funciona, porque generalmente cambia el orden de las llamadas. Sin embargo, extraer matchers a métodos funciona muy bien.
int between10And20 = and(gt(10), lt(20)); /* BAD */ when(foo.quux(anyInt(), between10And20)).thenReturn(true); // Mockito sees the stack as the opposite: and(gt(10), lt(20)), anyInt(). public static int anyIntBetween10And20() { return and(gt(10), lt(20)); } /* OK */ when(foo.quux(anyInt(), anyIntBetween10And20())).thenReturn(true); // The helper method calls the matcher methods in the right order.
La pila cambia con la frecuencia suficiente como para que Mockito no pueda controlarla con mucho cuidado. Solo puede verificar la pila cuando interactúas con Mockito o un simulacro, y tiene que aceptar emparejadores sin saber si se usan inmediatamente o si se abandonan accidentalmente. En teoría, la pila siempre debe estar vacía fuera de una llamada para
when
overify
, pero Mockito no puede verificar eso automáticamente. Puede verificar manualmente conMockito.validateMockitoUsage()
.En una llamada a
when
, Mockito realmente llama al método en cuestión, que arrojará una excepción si ha anulado el método para lanzar una excepción (o si requiere valores distintos de cero o no nulos).doReturn
ydoAnswer
(etc) no invocan el método real y suelen ser una alternativa útil.Si había llamado a un método simulado en el medio del troceo (por ejemplo, para calcular una respuesta para un matcher
eq
), Mockito verificaría la longitud de la pila contra esa llamada en su lugar, y probablemente falle.Si intentas hacer algo mal, como anotar / verificar un método final , Mockito llamará al método real y también dejará emparejamientos adicionales en la pila . Es posible que la llamada al método
final
no arroje una excepción, pero es posible que obtenga una InvalidUseOfMatchersException de los emparejadores perdidos la próxima vez que interactúe con un simulacro.
Problemas comunes
InvalidUseOfMatchersException :
Verifique que cada argumento tenga exactamente una llamada de emparejador, si utiliza los acertijos y que no ha utilizado un acuse de recibo fuera de una llamada de
verify
owhen
. Los Matchers nunca deben usarse como valores devueltos o campos / variables.Compruebe que no está llamando a un simulacro como parte de proporcionar un argumento de coincidencia.
Verifique que no esté intentando resguardar / verificar un método final con un matcher. Es una gran manera de dejar un emparejador en la pila, y a menos que tu método final arroje una excepción, esta podría ser la única vez que te des cuenta de que el método del que te estás burlando es definitivo.
NullPointerException con argumentos primitivos:
(Integer) any()
devuelve null mientras queany(Integer.class)
devuelve 0; esto puede causar unaNullPointerException
si está esperando unint
lugar de un entero. En cualquier caso, prefieraanyInt()
, que devolverá cero y también omitirá el paso de auto-boxing.NullPointerException u otras excepciones: llamadas a
when(foo.bar(any())).thenReturn(baz)
realidad llama afoo.bar(null)
, que podría haber tropezado para lanzar una excepción al recibir un argumento nulo. Cambiando adoReturn(baz).when(foo).bar(any())
salta el comportamiento del stubbed .
Solución de problemas generales
Utilice MockitoJUnitRunner , o llame explícitamente a
validateMockitoUsage
en su métodotearDown
otearDown
(que el corredor haría por usted automáticamente). Esto ayudará a determinar si ha utilizado mal los emparejadores.Para fines de depuración, agregue llamadas a
validateMockitoUsage
en su código directamente. Esto arrojará si tiene algo en la pila, que es una buena advertencia de un mal síntoma.