Usando Mockito para simular clases con parámetros genéricos

280

¿Existe un método limpio de burlarse de una clase con parámetros genéricos? Digamos que tengo que burlarme de una clase Foo<T>que necesito pasar a un método que espera a Foo<Bar>. Puedo hacer lo siguiente con bastante facilidad:

Foo mockFoo = mock(Foo.class);
when(mockFoo.getValue).thenReturn(new Bar());

Suponiendo que getValue()devuelve el tipo genérico T. Pero eso tendrá gatitos cuando más tarde lo pase a un método esperando Foo<Bar>. ¿Es el casting el único medio para hacer esto?

Tim Clemons
fuente

Respuestas:

280

Creo que necesitas lanzarlo, pero no debería ser tan malo:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
John Paulett
fuente
34
Sí, pero aún tienes una advertencia. ¿Es posible evitar la advertencia?
Odwl
12
@SuppressWarnings ("sin marcar")
qualidafial
18
Creo que esto es totalmente aceptable ya que estamos hablando de un objeto simulado en una prueba unitaria.
Magnilex
1
@demaniak No funciona en absoluto. Los comparadores de argumentos no se pueden usar en ese contexto.
Krzysztof Krasoń
1
@demaniak Eso compilará bien, pero al ejecutar la prueba arrojará InvalidUseOfMatchersException (que es una RuntimeException)
Superole
277

Otra forma de evitar esto es usar @Mockanotaciones en su lugar. No funciona en todos los casos, pero se ve mucho más sexy :)

Aquí hay un ejemplo:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

    @Test
    public void testFoo() {
        when(fooMock.getValue()).thenReturn(new Bar());
    }
}

El MockitoJUnitRunnerinicializa los campos con anotada @Mock.

Marek Kirejczyk
fuente
3
esto está en desuso en 1.9.5. :( Me parece mucho más limpio.
Código Noviciado
12
@CodeNovitiate No pude encontrar anotaciones de desaprobación en MockitoJUnitRunner y Mock en 1.9.5. Entonces, ¿qué está en desuso? (Sí, org.mockito.MockitoAnnotations.Mock está en desuso, pero debería usar org.mockito.Mock en su lugar)
neu242
12
Bien hecho, esto funcionó perfectamente para mí. No es solo "más sexy", evita una advertencia sin usar SuppressWarnings. Las advertencias existen por una razón, es mejor no tener el hábito de suprimirlas. ¡Gracias!
Nicole
44
Hay una cosa que no me gusta usar en @Mocklugar de mock(): los campos siguen siendo nulos durante el tiempo de construcción, por lo que no puedo insertar dependencias en ese momento y no puedo hacer que los campos sean definitivos. El primero se puede resolver mediante un @Beforemétodo anotado, por supuesto.
Rüdiger Schulz
3
Para la iniciación solo llame a MockitoAnnotations.initMocks (this);
borjab
42

Siempre puede crear una clase / interfaz intermedia que satisfaga el tipo genérico que desea especificar. Por ejemplo, si Foo fuera una interfaz, podría crear la siguiente interfaz en su clase de prueba.

private interface FooBar extends Foo<Bar>
{
}

En situaciones en las que Foo es una clase no final , puede extender la clase con el siguiente código y hacer lo mismo:

public class FooBar extends Foo<Bar>
{
}

Entonces podría consumir cualquiera de los ejemplos anteriores con el siguiente código:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
dsingleton
fuente
44
Siempre que Foosea ​​una interfaz o clase no final, esta parece ser una solución razonablemente elegante. Gracias.
Tim Clemons
Actualicé la respuesta para incluir ejemplos de clases no finales también. Idealmente, estaría codificando contra una interfaz, pero ese no siempre será el caso. ¡Buena atrapada!
dsingleton
16

Crea un método de utilidad de prueba . Especialmente útil si lo necesita por más de una vez.

@Test
public void testMyTest() {
    // ...
    Foo<Bar> mockFooBar = mockFoo();
    when(mockFooBar.getValue).thenReturn(new Bar());

    Foo<Baz> mockFooBaz = mockFoo();
    when(mockFooBaz.getValue).thenReturn(new Baz());

    Foo<Qux> mockFooQux = mockFoo();
    when(mockFooQux.getValue).thenReturn(new Qux());
    // ...
}

@SuppressWarnings("unchecked") // still needed :( but just once :)
private <T> Foo<T> mockFoo() {
    return mock(Foo.class);
}
acdcjunior
fuente
Podría extender su respuesta para hacer que un método de utilidad general pase la clase que desea burlarse.
William Dutton
1
@WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }ni siquiera necesita una sola supresión :) Pero ten cuidado, Integer num = genericMock(Number.class)compila, pero tira ClassCastException. Esto solo es útil para el G<P> mock = mock(G.class)caso más común .
TWiStErRob
6

Estoy de acuerdo en que no se deben suprimir las advertencias en clases o métodos, ya que se podrían pasar por alto otras advertencias suprimidas accidentalmente. Pero en mi humilde opinión, es absolutamente razonable suprimir una advertencia que afecta solo a una sola línea de código.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
Tobias Uhmann
fuente
3

Aquí hay un caso interesante: el método recibe una colección genérica y devuelve una colección genérica del mismo tipo base. Por ejemplo:

Collection<? extends Assertion> map(Collection<? extends Assertion> assertions);

Este método se puede burlar con la combinación de Mockito anyCollectionOf matcher y la respuesta.

when(mockedObject.map(anyCollectionOf(Assertion.class))).thenAnswer(
     new Answer<Collection<Assertion>>() {
         @Override
         public Collection<Assertion> answer(InvocationOnMock invocation) throws Throwable {
             return new ArrayList<Assertion>();
         }
     });
qza
fuente