Usando o Mockito para simular classes com parâmetros genéricos

280

Existe um método limpo de zombar de uma classe com parâmetros genéricos? Digamos que eu tenha que zombar de uma classe Foo<T>que preciso passar para um método que espera a Foo<Bar>. Eu posso fazer o seguinte com bastante facilidade:

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

Assumindo getValue()retorna o tipo genérico T. Mas isso vai ter gatinhos quando mais tarde passar para um método esperado Foo<Bar>. O elenco é o único meio de fazer isso?

Tim Clemons
fonte

Respostas:

280

Eu acho que você precisa transmiti-lo, mas não deve ser tão ruim assim:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
John Paulett
fonte
34
Sim, mas você ainda tem um aviso. É possível evitar o aviso?
odwl 31/08/10
12
@SuppressWarnings ("unchecked")
qualidafial
18
Eu acho que isso é totalmente aceitável, já que estamos falando de um objeto falso em um teste de unidade.
Magnilex
1
@demaniak Não funciona. Os correspondentes de argumento não podem ser usados ​​nesse contexto.
Krzysztof Krasoń
1
@demaniak Isso irá compilar muito bem, mas ao executar o teste ele vai jogar InvalidUseOfMatchersException (que é uma RuntimeException)
Superole
277

Uma outra maneira de contornar isso é usar a @Mockanotação. Não funciona em todos os casos, mas parece muito mais sexy :)

Aqui está um exemplo:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

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

O MockitoJUnitRunnerinicializa os campos anotados com @Mock.

Marek Kirejczyk
fonte
3
isso está obsoleto no 1.9.5. :( Parece muito mais limpo para mim.
Noviciado de código 27/03
12
@CodeNovitiate Não consegui encontrar nenhuma anotação de descontinuação no MockitoJUnitRunner e Mock no 1.9.5. Então, o que é preterido? (Sim, org.mockito.MockitoAnnotations.Mock está obsoleta, mas você deve usar org.mockito.Mock vez)
neu242
12
Muito bem, isso funcionou perfeitamente para mim. Não é apenas "mais sexy", evita um aviso sem usar SuppressWarnings. Os avisos existem por uma razão, é melhor não ter o hábito de suprimi-los. Obrigado!
Nicole
4
Há uma coisa que eu não gosto de usar em @Mockvez de mock(): os campos ainda são nulos durante o tempo de construção; portanto, não posso inserir dependências naquele momento e não pode tornar os campos finais. O primeiro pode ser resolvido por um @Beforemétodo não anotado, é claro.
Rüdiger Schulz
3
Para iniciação, basta chamar MockitoAnnotations.initMocks (this);
amigos estão dizendo sobre borjab
42

Você sempre pode criar uma classe / interface intermediária que satisfaça o tipo genérico que deseja especificar. Por exemplo, se Foo fosse uma interface, você poderia criar a seguinte interface na sua classe de teste.

private interface FooBar extends Foo<Bar>
{
}

Nas situações em que Foo é uma classe não final , você pode estender a classe com o seguinte código e fazer o mesmo:

public class FooBar extends Foo<Bar>
{
}

Em seguida, você pode consumir um dos exemplos acima com o seguinte código:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
dsingleton
fonte
4
Desde que Fooseja uma interface ou classe não final, essa parece ser uma solução razoavelmente elegante. Obrigado.
amigos estão dizendo sobre tim
Atualizei a resposta para incluir também exemplos de aulas não finais. Idealmente, você codificaria contra uma interface, mas nem sempre será esse o caso. Boa pegada!
dsingleton
16

Crie um método utilitário de teste . Especialmente útil se você precisar mais de uma 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
fonte
Pode estender sua resposta para fazer com que um método de utilidade geral passe na classe que você deseja zombar.
22716 William Williamtontonton
1
@WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }nem precisa de uma única supressão :) Mas tenha cuidado, Integer num = genericMock(Number.class)compila, mas joga ClassCastException. Isso é útil apenas para o G<P> mock = mock(G.class)caso mais comum .
TWIStErRob
6

Concordo que não se deve suprimir avisos em classes ou métodos, pois é possível ignorar outros avisos suprimidos acidentalmente. Mas IMHO é absolutamente razoável suprimir um aviso que afeta apenas uma única linha de código.

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

Aqui está um caso interessante: o método recebe coleção genérica e retorna coleção genérica do mesmo tipo base. Por exemplo:

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

Este método pode ser ridicularizado com a combinação do Mockito anyCollectionOf matcher e a resposta.

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
fonte