Mockito to metody statyczne i wywołania tych metod, które zastępują argumenty podczas wywołań funkcji when
i verify
.
Dopasowywanie Hamcresta (wersja zarchiwizowana) (lub dopasowywanie w stylu Hamcresta) to bezstanowe instancje obiektów ogólnego przeznaczenia, które implementują Matcher<T>
i ujawniają metodę, matches(T)
która zwraca wartość true, jeśli obiekt spełnia kryteria dopasowania. Mają być wolne od skutków ubocznych i są zwykle używane w twierdzeniach, takich jak poniższe.
/* Mockito */ verify(foo).setPowerLevel(gt(9000));
/* Hamcrest */ assertThat(foo.getPowerLevel(), is(greaterThan(9000)));
Dopasowania Mockito istnieją niezależnie od dopasowań w stylu Hamcresta, więc opisy wyrażeń dopasowujących pasują bezpośrednio do wywołań metod : dopasowujące Mockito zwracają T
tam, gdzie metody dopasowujące Hamcrest zwracają obiekty dopasowujące (typuMatcher<T>
.
Dopasowujących Mockito są wywoływane za pomocą metod statycznych, takich jak eq
, any
,gt
, i startsWith
na org.mockito.Matchers
i org.mockito.AdditionalMatchers
. Istnieją również adaptery, które zmieniły się w wersjach Mockito:
- W przypadku Mockito 1.x,
Matchers
niektóre wywołania (takie jak intThat
lub argThat
) są dopasowaniami Mockito, które bezpośrednio akceptują dopasowania Hamcrest jako parametry.ArgumentMatcher<T>
rozszerzony org.hamcrest.Matcher<T>
, który był używany w wewnętrznej reprezentacji Hamcrest i był podstawową klasą dopasowującą Hamcrest zamiast dowolnego rodzaju Mockito.
- W przypadku Mockito 2.0+ Mockito nie jest już bezpośrednio zależne od Hamcresta.
Matchers
wywołania wyrażone jako intThat
lub argThat
zawijające ArgumentMatcher<T>
obiekty, które już nie implementują, org.hamcrest.Matcher<T>
ale są używane w podobny sposób. Adaptery do ścięgien, takie jak argThat
iintThat
są nadal dostępne, ale MockitoHamcrest
zamiast tego przeniosły się .
Niezależnie od tego, czy dopasowujące są ścięgnami, czy po prostu w stylu Hamcrest, można je dostosować w następujący sposób:
/* Mockito matcher intThat adapting Hamcrest-style matcher is(greaterThan(...)) */
verify(foo).setPowerLevel(intThat(is(greaterThan(9000))));
W powyższym stwierdzeniu: foo.setPowerLevel
to metoda, która akceptuje rozszerzenie int
. is(greaterThan(9000))
zwraca a Matcher<Integer>
, co nie działałoby jako setPowerLevel
argument. Dopasowanie Mockito intThat
owija to dopasowanie w stylu Hamcrest i zwraca int
tak to mógł pojawić się jako argument; Mockito dopasowujące się, takie jakgt(9000)
całe wyrażenie w jedno wywołanie, tak jak w pierwszym wierszu przykładowego kodu.
Co dopasowują / zwracają
when(foo.quux(3, 5)).thenReturn(true);
Kiedy nie używa dopasowywania argumentów, Mockito rejestruje wartości argumentów i porównuje je z ich equals
metodami.
when(foo.quux(eq(3), eq(5))).thenReturn(true); // same as above
when(foo.quux(anyInt(), gt(5))).thenReturn(true); // this one's different
Kiedy dzwonisz do dopasowującego, takiego jak any
lub gt
(większe niż), Mockito przechowuje obiekt dopasowujący, który powoduje, że Mockito pomija sprawdzanie równości i stosuje wybrane dopasowanie. W przypadkuargumentCaptor.capture()
tego przechowuje dopasowania, które zamiast tego zapisuje swój argument do późniejszej inspekcji.
Dopasowania zwracają fikcyjne wartości, takie jak zero, puste kolekcje lub null
. Mockito próbuje zwrócić bezpieczną, odpowiednią wartość fikcyjną, taką jak 0 dla anyInt()
lub any(Integer.class)
lub pusty List<String>
dla anyListOf(String.class)
. Jednak z powodu wymazywania typów Mockito nie ma informacji o typie, aby zwrócić jakąkolwiek wartość oprócz null
for any()
lubargThat(...)
, co może spowodować NullPointerException jeśli próbuje „Auto-unbox” a null
pierwotną wartość.
Mecze lubią eq
i gt
przyjmują wartości parametrów; w idealnym przypadku wartości te powinny zostać obliczone przed rozpoczęciem tworzenia kodu pośredniczącego / weryfikacji. Wywołanie kpiny w trakcie kpiny z innego połączenia może kolidować z klepaniem.
Metody dopasowujące nie mogą być używane jako wartości zwracane; nie ma sposobu na wyrażenie thenReturn(anyInt())
lubthenReturn(any(Foo.class))
w Mockito. Mockito musi dokładnie wiedzieć, do której instancji powrócić w wywołaniach pośredniczących i nie wybierze dla Ciebie arbitralnej wartości zwracanej.
Szczegóły dotyczące wdrożenia
Dopasowania są przechowywane (jako elementy dopasowujące obiekty w stylu Hamcrest) w stosie zawartym w klasie o nazwie ArgumentMatcherStorage . MockitoCore i Matchers posiadają instancję ThreadSafeMockingProgress , która statycznie zawiera ThreadLocal przechowującą instancje MockingProgress. To ten MockingProgressImpl zawiera konkret ArgumentMatcherStorageImpl . W związku z tym stan makiety i dopasowywania jest statyczny, ale spójny w zakresie wątku między klasami Mockito i Matchers.
Większość połączeń Matcher tylko dodać do tego stosu, z wyjątkiem dla dopasowujących jak and
, or
inot
. To doskonale odpowiada (i polega na) kolejności oceny w Javie , która ocenia argumenty od lewej do prawej przed wywołaniem metody:
when(foo.quux(anyInt(), and(gt(10), lt(20)))).thenReturn(true);
[6] [5] [1] [4] [2] [3]
To będzie:
- Dodaj
anyInt()
do stosu.
- Dodaj
gt(10)
do stosu.
- Dodaj
lt(20)
do stosu.
- Usunąć
gt(10)
i lt(20)
dodać and(gt(10), lt(20))
.
- Call
foo.quux(0, 0)
, które (o ile nie jest to skrótowe) zwraca wartość domyślną false
. Wewnętrznie Mockito oznacza quux(int, int)
ostatnie połączenie.
- Call
when(false)
, które odrzuca swój argument i przygotowuje do metody stub quux(int, int)
określonej w 5. Jedyne dwa prawidłowe stany mają długość stosu 0 (równość) lub 2 (dopasowywanie), a na stosie są dwa dopasowania (kroki 1 i 4), więc Mockito odcina metodę z any()
dopasowaniem dla pierwszego argumentu i and(gt(10), lt(20))
drugiego argumentu i czyści stos.
To pokazuje kilka zasad:
Mockito nie potrafi odróżnić quux(anyInt(), 0)
i quux(0, anyInt())
. Oba wyglądają jak wezwanie do quux(0, 0)
z jednym dopasowaniem int na stosie. W konsekwencji, jeśli używasz jednego dopasowania, musisz dopasować wszystkie argumenty.
Kolejność połączeń jest nie tylko ważna, ale sprawia, że wszystko działa . Wyodrębnianie dopasowań do zmiennych zwykle nie działa, ponieważ zwykle zmienia kolejność wywołań. Jednak wyodrębnianie dopasowań do metod działa świetnie.
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.
Stos zmienia się na tyle często, że Mockito nie może go bardzo dokładnie nadzorować. Może sprawdzać stos tylko podczas interakcji z Mockito lub makietą i musi akceptować dopasowania, nie wiedząc, czy zostaną użyte natychmiast, czy przypadkowo porzucone. Teoretycznie stos powinien być zawsze pusty poza wywołaniem when
lub verify
, ale Mockito nie może tego sprawdzić automatycznie. Możesz sprawdzić ręcznie za pomocą Mockito.validateMockitoUsage()
.
W wywołaniu funkcji when
Mockito w rzeczywistości wywołuje daną metodę, co spowoduje zgłoszenie wyjątku, jeśli metoda została skrócona, aby zgłosić wyjątek (lub wymaga wartości niezerowych lub niezerowych).
doReturn
i doAnswer
(itp.) nie wywołują rzeczywistej metody i często są użyteczną alternatywą.
Gdybyś eq
wywołał metodę pozorowaną w trakcie odgrywania kodu (np. W celu obliczenia odpowiedzi dla dopasowania), Mockito sprawdziłby zamiast tego długość stosu w porównaniu z tym wywołaniem i prawdopodobnie się nie uda.
Jeśli spróbujesz zrobić coś złego, na przykład zgarnąć / zweryfikować ostateczną metodę , Mockito wywoła prawdziwą metodę, a także pozostawi dodatkowe dopasowania na stosie . final
Wywołanie metody mogą nie wyjątek, ale może pojawić się InvalidUseOfMatchersException od bezpańskich dopasowujących przy następnym interakcji z mock.
Częste problemy
InvalidUseOfMatchersException :
Sprawdź, czy każdy argument ma dokładnie jedno wywołanie dopasowujące, jeśli w ogóle używasz dopasowań i czy nie użyłeś dopasowania poza wywołaniem when
lub verify
. Dopasowania nigdy nie powinny być używane jako skrócone wartości zwracane lub pola / zmienne.
Sprawdź, czy nie wywołujesz mocka jako części argumentu dopasowującego.
Sprawdź, czy nie próbujesz odgiąć / zweryfikować ostatecznej metody za pomocą dopasowania. To świetny sposób na pozostawienie dopasowania na stosie i chyba że Twoja ostatnia metoda rzuci wyjątek, może to być jedyny moment, w którym zdasz sobie sprawę, że metoda, z której kpisz, jest ostateczna.
NullPointerException z argumentami pierwotnymi: (Integer) any()
zwraca null, podczas gdy any(Integer.class)
zwraca 0; może to spowodować, NullPointerException
jeśli spodziewasz się an int
zamiast liczby całkowitej. W każdym razie preferuj anyInt()
, co zwróci zero, a także pominie krok automatycznego pakowania.
NullPointerException lub inne wyjątki: domaga się when(foo.bar(any())).thenReturn(baz)
rzeczywiście nazwać foo.bar(null)
, co może masz zgaszone rzucić wyjątek podczas odbierania zerowy argument. Przełączenie na doReturn(baz).when(foo).bar(any())
pomija zachowanie skrótów .
Ogólne rozwiązywanie problemów
Użyj MockitoJUnitRunner lub jawnie wywołaj validateMockitoUsage
w swojej metodzie tearDown
lub @After
(co runner zrobi za Ciebie automatycznie). Pomoże to ustalić, czy nadużyłeś dopasowań.
W celu debugowania dodaj wywołania do validateMockitoUsage
bezpośrednio w kodzie. Spowoduje to wyrzucenie, jeśli masz coś na stosie, co jest dobrym ostrzeżeniem o złym objawie.
To tylko mały dodatek do doskonałej odpowiedzi Jeffa Bowmana, ponieważ znalazłem to pytanie, szukając rozwiązania jednego z moich własnych problemów:
Jeśli wywołanie metody pasuje do więcej niż jednego
when
wytrenowanego wywołania pozorowanego , kolejnośćwhen
wywołań jest ważna i powinna być od najszerszej do najbardziej szczegółowej. Zaczynając od jednego z przykładów Jeffa:to kolejność zapewniająca (prawdopodobnie) pożądany rezultat:
Jeśli odwrócisz wywołania when, wynik zawsze będzie
true
.źródło