Jak działa wywołanie mockito when ()?

111

Biorąc pod uwagę następujące oświadczenie Mockito:

when(mock.method()).thenReturn(someValue);

W jaki sposób Mockito tworzy coś dla makiety, biorąc pod uwagę, że instrukcja mock.method () przekaże wartość zwracaną do when ()? Wyobrażam sobie, że wykorzystuje to trochę rzeczy CGLib, ale chciałbym wiedzieć, jak to jest technicznie zrobione.

marchaos
źródło

Odpowiedzi:

118

Krótka odpowiedź jest taka, że ​​w twoim przykładzie wynik mock.method()będzie odpowiednią dla typu wartością pustą; mockito używa pośrednictwa poprzez proxy, przechwytywanie metody i współdzieloną instancję MockingProgressklasy w celu ustalenia, czy wywołanie metody na makiecie służy do wstawiania lub odtwarzania istniejącego zachowania, a nie do przekazywania informacji o skrótach za pośrednictwem wartości zwracanej wyszydzana metoda.

Mini-analiza w ciągu kilku minut patrząc na kod mockito jest następująca. Uwaga, jest to bardzo przybliżony opis - w grze jest wiele szczegółów. Sugeruję, abyś sam sprawdził źródło na githubie .

Po pierwsze, kiedy kpisz z klasy przy użyciu mockmetody Mockitoklasy, zasadniczo dzieje się tak:

  1. Mockito.mockdelegaci do org.mockito.internal.MockitoCore.mock, przekazując domyślne ustawienia makiety jako parametr.
  2. MockitoCore.mockdelegatów do org.mockito.internal.util.MockUtil.createMock
  3. MockUtilKlasa wykorzystuje ClassPathLoaderklasę, aby uzyskać instancję MockMakerużyć do tworzenia makiety. Domyślnie używana jest klasa CgLibMockMaker .
  4. CgLibMockMakerużywa klasy zapożyczonej z JMock, ClassImposterizerktóra obsługuje tworzenie makiety. Kluczowe elementy użytej „magii mockito” są MethodInterceptorużywane do tworzenia makiety: mockito MethodInterceptorFilteri łańcuch instancji MockHandler, w tym instancja MockHandlerImpl . Przechwytywacz metody przekazuje wywołania do instancji MockHandlerImpl, która implementuje logikę biznesową, która powinna być zastosowana, gdy metoda jest wywoływana na makiecie (tj. Wyszukuje, czy odpowiedź została już zarejestrowana, czy wywołanie reprezentuje nowy kod pośredniczący itp. Stan domyślny jest taki, że jeśli kod pośredniczący nie jest jeszcze zarejestrowany dla wywoływanej metody, zwracana jest pusta wartość odpowiednia dla typu .

Spójrzmy teraz na kod w Twoim przykładzie:

when(mock.method()).thenReturn(someValue)

Oto kolejność, w jakiej ten kod będzie wykonywany:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

Kluczem do zrozumienia tego, co się dzieje, jest to, co się dzieje, gdy wywoływana jest metoda na makiecie: przechwytywacz metody przekazuje informacje o wywołaniu metody i deleguje je do łańcucha MockHandlerinstancji, które ostatecznie są delegowane MockHandlerImpl#handle. Podczas MockHandlerImpl#handle, pozorna procedura obsługi tworzy instancję OngoingStubbingImpli przekazuje ją do udostępnionej MockingProgressinstancji.

Gdy whenmetoda jest wywoływana po wywołaniu method(), jest delegowana do MockitoCore.when, co wywołuje stub()metodę tej samej klasy. Ta metoda rozpakowuje trwający kod pośredniczący ze współużytkowanej MockingProgressinstancji, do której method()zapisano fałszywe wywołanie, i zwraca je. Następnie thenReturnwywoływana jest metoda na OngoingStubbinginstancji.

Paul Morie
źródło
1
Dzięki za szczegółową odpowiedź. Kolejne pytanie - wspominasz, że „kiedy metoda jest wywoływana po wywołaniu metody ()” - skąd wie, że wywołanie when () jest następnym wywołaniem (lub zawija) wywołanie metody ()? Mam nadzieję, że to ma sens.
marchaos
@marchaos To nie wie. Ze when(mock.method()).thenXyz(...)składnią mock.method()zostaje wykonany w trybie „replay”, a nie „stubbing”. Normalnie, to wykonanie mock.method()nie ma żadnego wpływu, więc później, kiedy thenXyz(...)( thenReturn, thenThrow, thenAnsweritp) zostanie wykonany, to idzie na „stubbing” tryb, a następnie rejestruje pożądanego rezultatu dla tego wywołania metody.
Rogério,
1
Rogerio, w rzeczywistości jest trochę bardziej subtelny - mockito nie ma wyraźnych trybów stubbing i powtórek. Zmienię odpowiedź później, aby była bardziej przejrzysta.
Paul Morie,
Podsumowując, w skrócie, łatwiej jest przechwycić wywołanie metody w innej metodzie za pomocą CGLIB lub Javassist, aby przechwycić, powiedzmy, operator „if”.
Infeligo,
Nie wmasowałem tu jeszcze swojego opisu, ale też o nim nie zapomniałem. FYI.
Paul Morie
33

Krótka odpowiedź brzmi: za kulisami Mockito używa pewnego rodzaju zmiennych globalnych / pamięci do zapisywania informacji o krokach budowania kodu metody (wywołanie method (), when (), thenReturn () w twoim przykładzie), aby ostatecznie można było stwórz mapę tego, co powinno zostać zwrócone, kiedy wywoływana jest nazwa parametru.

Uważam, że ten artykuł jest bardzo pomocny: Wyjaśnienie, jak działają Mock Framework oparte na proxy ( http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html ). Autor zaimplementował demonstracyjny framework Mocking, który uważam za bardzo dobre źródło informacji dla osób, które chcą dowiedzieć się, jak działają te frameworki Mocking.

Moim zdaniem jest to typowe zastosowanie Anti-Pattern. Zwykle powinniśmy unikać „efektu ubocznego”, kiedy implementujemy metodę, co oznacza, że ​​metoda powinna akceptować dane wejściowe, wykonywać obliczenia i zwracać wynik - poza tym nic się nie zmieniło. Ale Mockito tylko celowo narusza tę zasadę. Jego metody oprócz zwracania wyniku przechowują również kilka informacji: Mockito.anyString (), mockInstance.method (), when (), thenReturn, wszystkie mają specjalny „efekt uboczny”. Dlatego też framework na pierwszy rzut oka wygląda jak magia - zwykle nie piszemy takiego kodu. Jednak w przypadku fałszywego frameworka ten projekt przeciw wzorowi jest świetnym projektem, ponieważ prowadzi do bardzo prostego interfejsu API.

Jacob Wu
źródło
4
Doskonały link. Geniusz za tym jest taki: bardzo proste API, które sprawia, że ​​całość wygląda bardzo ładnie. Kolejną świetną decyzją jest to, że metoda when () używa typów ogólnych, dzięki czemu metoda thenReturn () jest bezpieczna pod względem typu.
David Tonhofer
Uważam, że to lepsza odpowiedź. W przeciwieństwie do drugiej odpowiedzi, jasno wyjaśnia koncepcje procesu pozorowania zamiast przepływu sterowania przez konkretny kod. Zgadzam się, doskonały link.
mihca