Saltar al contenido

¿Cómo funcionan los emparejadores de Mockito?

Este equipo especializado pasados muchos días de trabajo y de recopilar de datos, hallamos la solución, esperamos que resulte útil para ti para tu proyecto.

Solución:

Los emparejadores de Mockito son static métodos y llamadas a esos métodos, que sustituye a los argumentos durante las llamadas a when y verify.

Los emparejadores de Hamcrest (versión archivada) (o los emparejadores de estilo Hamcrest) son instancias de objetos de propósito general sin estado que implementan Matcher y exponer un método matches(T) que vuelve true si el objeto coincide con los criterios del Matcher. Están destinados a estar libres de efectos secundarios y generalmente se usan en afirmaciones como la que se muestra a continuación.

/* Mockito */  verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));

Los emparejadores Mockito existen, separados de los emparejadores estilo Hamcrest, para que las descripciones de las expresiones coincidentes encajen directamente en las invocaciones de métodos: Vuelven los emparejadores de Mockito T donde los métodos Matcher de Hamcrest devuelven objetos Matcher (de tipo Matcher).

Los emparejadores de Mockito se invocan a través de static métodos como eq, any, gt, y startsWith sobre 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 (como intThat o argThat) son comparadores de Mockito que aceptan directamente los comparadores de Hamcrest como parámetros. ArgumentMatcher extendido org.hamcrest.Matcher, que se utilizó en la representación interna de Hamcrest y fue un Clase base del emparejador Hamcrest en lugar de cualquier tipo de emparejador Mockito.
  • Para Mockito 2.0+, Mockito ya no tiene una dependencia directa de Hamcrest. Matchers llamadas expresadas como intThat o argThat envoltura ArgumentMatcher objetos que ya no implementan org.hamcrest.Matcher pero se usan de manera similar. Adaptadores Hamcrest como argThat y intThat todavía están disponibles, pero se han trasladado a MockitoHamcrest en lugar de.

Independientemente de si los matchers son Hamcrest o simplemente estilo Hamcrest, se pueden adaptar así:

/* 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 una int. is(greaterThan(9000)) devuelve un Matcher, que no funcionaría como setPowerLevel argumento. El emparejador Mockito intThat envuelve ese Matcher estilo Hamcrest y devuelve un int por lo que pueden aparecer como un argumento; A los emparejadores de Mockito les gusta gt(9000) envolvería toda esa expresión en una sola llamada, como en la primera línea del código de ejemplo.

¿Qué hacen / devuelven los matchers?

when(foo.quux(3, 5)).thenReturn(true);

Cuando no utiliza comparadores de argumentos, Mockito registra los valores de sus argumentos y los compara con sus equals métodos.

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 comparador como any o gt (mayor que), Mockito almacena un objeto de coincidencia que hace que Mockito omita esa verificación de igualdad y aplique la coincidencia de su elección. En el caso de argumentCaptor.capture() almacena un comparador que guarda su argumento para una inspección posterior.

Los comparadores devuelven valores ficticios como cero, colecciones vacías o null. Mockito intenta devolver un valor ficticio apropiado y seguro, como 0 para anyInt() o any(Integer.class) o un vacio List por anyListOf(String.class). Sin embargo, debido al borrado de tipo, Mockito carece de información de tipo para devolver cualquier valor excepto null por any() o argThat(...), que puede causar una NullPointerException si se intenta “desempaquetar automáticamente” un null valor primitivo.

Matchers como eq y gt tomar valores de parámetros; idealmente, estos valores deben calcularse antes de que comience la verificación / apéndice. Hacer un simulacro en el medio de burlarse de otra llamada puede interferir con el stubbing.

Los métodos de comparación no se pueden utilizar como valores de retorno; no hay forma de expresarlo thenReturn(anyInt()) o thenReturn(any(Foo.class)) en Mockito, por ejemplo. Mockito necesita saber exactamente qué instancia devolver en llamadas de stubbing y no elegirá un valor de retorno arbitrario por usted.

Detalles de implementacion

Los comparadores se almacenan (como comparadores 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 inactivamente contiene un ThreadLocal que contiene instancias de MockingProgress. Es este MockingProgressImpl que contiene un ArgumentMatcherStorageImpl concreto. En consecuencia, el estado simulado y coincidente es static pero con alcance de hilo de forma coherente entre las clases Mockito y Matchers.

La mayoría de las llamadas de comparadores solo se agregan a esta pila, con una excepción para los comparadores como and, or, y not. Esto se corresponde perfectamente con (y se basa en) 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:

  1. Agregar anyInt() a la pila.
  2. Agregar gt(10) a la pila.
  3. Agregar lt(20) a la pila.
  4. Eliminar gt(10) y lt(20) y añadir and(gt(10), lt(20)).
  5. Llama foo.quux(0, 0), que (a menos que se especifique lo contrario) devuelve el valor predeterminado false. Marcas internas de Mockito quux(int, int) como la llamada más reciente.
  6. Llama when(false), que descarta su argumento y se prepara para el método stub quux(int, int) identificado en 5. Los únicos dos estados válidos son con una longitud de pila 0 (igualdad) o 2 (emparejamientos), y hay dos emparejamientos en la pila (pasos 1 y 4), por lo que Mockito apila el método con un any() matcher para su primer argumento y and(gt(10), lt(20)) para su segundo argumento y borra la pila.

Esto demuestra algunas reglas:

  • Mockito no puede diferenciar entre quux(anyInt(), 0) y quux(0, anyInt()). Ambos parecen una llamada a quux(0, 0) con un matcher int en la pila. En consecuencia, si usa un comparador, debe hacer coincidir todos los argumentos.

  • El orden de las llamadas no solo es importante, es que hace que todo esto funcione. La extracción de comparadores a variables generalmente no funciona, porque generalmente cambia el orden de las llamadas. Sin embargo, extraer comparadores 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 tanta frecuencia que Mockito no puede controlarla con mucho cuidado. Solo puede verificar la pila cuando interactúas con Mockito o un simulacro, y tiene que aceptar comparadores sin saber si se usan de inmediato o se abandonan accidentalmente. En teoría, la pila siempre debería estar vacía fuera de una llamada a when o verify, pero Mockito no puede verificarlo automáticamente. Puede verificar manualmente con Mockito.validateMockitoUsage().

  • En una llamada a when, Mockito en realidad llama al método en cuestión, que generará una excepción si ha eliminado el método para generar una excepción (o requiere que no sea cero o nonull valores).
    doReturn y doAnswer (etc) hacer no invocan el método real y suelen ser una alternativa útil.

  • Si ha llamado a un método simulado en medio de un stubbing (por ejemplo, para calcular una respuesta para un eq matcher), Mockito verificaría la longitud de la pila contra ese llamar en su lugar, y probablemente fallar.

  • Si intenta hacer algo malo, como apuntar / verificar un método final, Mockito llamará al método real y también dejar coincidencias adicionales en la pila. los final Es posible que la llamada al método no arroje una excepción, pero puede obtener 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 al emparejador, si usa emparejadores, y que no ha usado un emparejador fuera de un when o verify llama. Los comparadores nunca deben usarse como valores de retorno stubped o campos / variables.

    • Verifique que no esté llamando a un simulacro como parte de proporcionar un argumento de coincidencia.

    • Comprueba que no estás intentando apuntar / verificar un método final con un comparador. Es una excelente manera de dejar un comparador en la pila y, a menos que su método final arroje una excepción, esta podría ser la única vez que se dé cuenta de que el método del que se está burlando es definitivo.

  • NullPointerException con argumentos primitivos:(Integer) any() devoluciones null tiempo any(Integer.class) devuelve 0; esto puede causar un NullPointerException si estas esperando un int en lugar de un entero. En cualquier caso, prefiera anyInt(), que devolverá cero y también omitirá el paso de auto-boxing.

  • NullPointerException u otras excepciones: Llamadas a when(foo.bar(any())).thenReturn(baz) realmente llamafoo.bar(null), que es posible que haya modificado para generar una excepción al recibir un null argumento. Cambiar a doReturn(baz).when(foo).bar(any()) omite el comportamiento interrumpido.

Solución de problemas generales

  • Utilice MockitoJUnitRunner, o llame explícitamente validateMockitoUsage en tus tearDown o @After método (que el corredor haría por usted automáticamente). Esto ayudará a determinar si ha hecho un mal uso de los emparejadores.

  • Para fines de depuración, agregue llamadas a validateMockitoUsage en su código directamente. Esto arrojará si tiene algo en la pila, lo cual es una buena advertencia de un mal síntoma.

Solo una pequeña adición a la excelente respuesta de Jeff Bowman, ya que encontré esta pregunta cuando buscaba una solución a uno de mis propios problemas:

Si una llamada a un método coincide con más de un simulacro when llamadas entrenadas, el orden de la when Las llamadas son importantes y deben ser desde las más amplias hasta las más específicas. 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 asegura el (probablemente) resultado 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 when, el resultado siempre será true.

Si te mola el asunto, puedes dejar una sección acerca de qué le añadirías a este ensayo.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *