Używanie Mockito do kpienia z klas za pomocą ogólnych parametrów

280

Czy istnieje czysta metoda wyśmiewania klasy za pomocą ogólnych parametrów? Powiedz, że muszę wyśmiewać klasę, Foo<T>którą muszę przekazać do metody, która oczekuje Foo<Bar>. Z łatwością mogę wykonać następujące czynności:

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

Zakładając, getValue()zwraca typ ogólny T. Ale to będzie miało kocięta, kiedy później przekażę to do metody, która się spodziewa Foo<Bar>. Czy casting jest jedynym sposobem na zrobienie tego?

Tim Clemons
źródło

Odpowiedzi:

280

Myślę, że musisz go rzucić, ale nie powinno być tak źle:

Foo<Bar> mockFoo = (Foo<Bar>) mock(Foo.class);
when(mockFoo.getValue()).thenReturn(new Bar());
John Paulett
źródło
34
Tak, ale nadal masz ostrzeżenie. Czy można uniknąć ostrzeżenia?
odwl
12
@SuppressWarnings („niezaznaczone”)
Qualidafial
18
Myślę, że jest to w pełni akceptowalne, ponieważ mówimy o próbnym obiekcie w teście jednostkowym.
Magnilex,
1
@demaniak To w ogóle nie działa. W tym kontekście nie można używać dopasowań argumentów.
Krzysztof Krasoń,
1
@demaniak To dobrze się skompiluje, ale po uruchomieniu testu wyrzuci InvalidUseOfMatchersException (który jest RuntimeException)
Superole
277

Innym sposobem jest użycie @Mockadnotacji. Nie działa we wszystkich przypadkach, ale wygląda o wiele bardziej seksownie :)

Oto przykład:

@RunWith(MockitoJUnitRunner.class)
public class FooTests {

    @Mock
    public Foo<Bar> fooMock;

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

MockitoJUnitRunnerInicjuje pola z adnotacjami @Mock.

Marek Kirejczyk
źródło
3
jest to przestarzałe w 1.9.5. :( Wydaje mi się dużo czystszy.
Kod Nowicjat
12
@CodeNovitiate Nie mogłem znaleźć adnotacji o wycofaniu na MockitoJUnitRunner i Mock w wersji 1.9.5. Co jest przestarzałe? (Tak, org.mockito.MockitoAnnotations.Mock jest przestarzałe, ale zamiast tego należy użyć org.mockito.Mock)
neu242
12
Dobra robota, działało to dla mnie idealnie. To nie tylko „seksowniejsze”, pozwala uniknąć ostrzeżenia bez użycia SuppressWarnings. Ostrzeżenia istnieją z jakiegoś powodu, lepiej nie mieć w zwyczaju ich tłumienia. Dzięki!
Nicole,
4
Jest jedna rzecz, której nie lubię używać @Mockzamiast mock(): pola są nadal puste w czasie budowy, więc nie mogę wstawić zależności w tym czasie i nie mogę sprawić, by pola były ostateczne. To pierwsze rozwiązanie można @Beforeoczywiście rozwiązać za pomocą opatrzonych komentarzem metod.
Rüdiger Schulz
3
W celu inicjacji wystarczy wywołać MockitoAnnotations.initMocks (this);
borjab
42

Zawsze możesz utworzyć klasę pośrednią / interfejs, który spełnia typ ogólny, który chcesz określić. Na przykład, jeśli Foo był interfejsem, możesz utworzyć następujący interfejs w swojej klasie testowej.

private interface FooBar extends Foo<Bar>
{
}

W sytuacjach, gdzie Foo jest non-final class, można po prostu rozszerzyć klasę z następującego kodu i zrobić to samo:

public class FooBar extends Foo<Bar>
{
}

Następnie możesz wykorzystać jeden z powyższych przykładów z następującym kodem:

Foo<Bar> mockFoo = mock(FooBar.class);
when(mockFoo.getValue()).thenReturn(new Bar());
dsingleton
źródło
4
Pod warunkiem, że Foojest to interfejs lub klasa nieoficjalna, wydaje się, że jest to dość eleganckie rozwiązanie. Dzięki.
Tim Clemons,
Zaktualizowałem odpowiedź, aby zawierała również przykłady zajęć niekończących. Idealnie byłoby kodować na interfejsie, ale nie zawsze tak będzie. Dobry chwyt!
dsingleton
16

Utwórz testową metodę narzędzia . Szczególnie przydatne, jeśli potrzebujesz go więcej niż raz.

@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
źródło
Może rozszerzyć twoją odpowiedź, aby uczynić ogólną metodę użyteczności przechodzącą w klasie, którą chcesz wyśmiewać.
William Dutton,
1
@WilliamDutton static <T> T genericMock(Class<? super T> classToMock) { return (T)mock(classToMock); }nie potrzebuje nawet jednego tłumienia :) Ale bądź ostrożny, Integer num = genericMock(Number.class)kompiluje, ale rzuca ClassCastException. Jest to przydatne tylko w najczęstszym G<P> mock = mock(G.class)przypadku.
TWiStErRob
6

Zgadzam się, że nie należy tłumić ostrzeżeń w klasach lub metodach, ponieważ można przeoczyć inne, przypadkowo tłumione ostrzeżenia. Ale IMHO jest absolutnie uzasadnione, aby ukryć ostrzeżenie, które dotyczy tylko jednego wiersza kodu.

@SuppressWarnings("unchecked")
Foo<Bar> mockFoo = mock(Foo.class);
Tobias Uhmann
źródło
3

Oto ciekawy przypadek: metoda odbiera ogólną kolekcję i zwraca ogólną kolekcję tego samego typu podstawowego. Na przykład:

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

Metodę tę można wyśmiewać za pomocą kombinacji Mockito anyCollectionOf matcher i Answer.

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
źródło