Mockito: Próba szpiegowania metody wywołuje metodę oryginalną

351

Używam Mockito 1.9.0. Chcę kpić zachowanie dla jednej metody klasy w teście JUnit, więc mam

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

Problem polega na tym, że w drugiej linii myClassSpy.method1()jest wywoływany, co powoduje wyjątek. Jedynym powodem, dla którego używam makiet, jest to, że później, przy każdym myClassSpy.method1()wywołaniu, nie zostanie wywołana prawdziwa metoda i myResultsobiekt zostanie zwrócony.

MyClassjest interfejsem i myInstancejest jego implementacją, jeśli to ma znaczenie.

Co muszę zrobić, aby poprawić to zachowanie szpiegowskie?

Dave
źródło

Odpowiedzi:

610

Pozwól mi zacytować oficjalną dokumentację :

Ważna uwaga na szpiegowanie prawdziwych obiektów!

Czasami nie można użyć, gdy (Obiekt) służy do karczowania szpiegów. Przykład:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

W twoim przypadku wygląda to tak:

doReturn(resulstIWant).when(myClassSpy).method1();
Tomasz Nurkiewicz
źródło
27
Co się stanie, jeśli użyję tej metody, a moja oryginalna nadal będzie wywoływana? Czy może występować problem z parametrami, które przekazuję? Oto cały test: nazywa się metodę pastebin.com/ZieY790P send
Evgeni Petrov
26
@EvgeniPetrov, jeśli twoja oryginalna metoda jest nadal wywoływana, to prawdopodobnie dlatego, że oryginalna metoda jest ostateczna. Mockito nie kpi z ostatecznych metod i nie może cię ostrzec przed szyderstwem z ostatecznych metod.
MarcG,
1
czy jest to również możliwe w przypadku doThrow ()?
Gobliins
1
tak, niestety metody statyczne są niemożliwe do wyśmiewania i „nie do szpiegowania”. To, co robię, aby poradzić sobie z metodami statycznymi, to owinięcie metody wokół wywołania statycznego i użycie doNothing lub doReturn dla tej metody. W singletonach lub obiektach Scala przenoszę mięso logiki do klasy abstrakcyjnej, co daje mi możliwość posiadania alternatywnej klasy testowej dla obiektu, na którym mogę stworzyć szpiega.
Andrew Norman,
24
A co, jeśli nadal NIE zostanie wywołana NIE ostateczna i NIE statyczna metoda?
X-HuMan
27

Moja sprawa różni się od przyjętej odpowiedzi. Próbowałem wyśmiewać metodę private-package dla instancji, która nie istniała w tym pakiecie

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

i klasy testowe

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

Kompilacja jest poprawna, ale gdy próbuje skonfigurować test, zamiast tego wywołuje prawdziwą metodę.

Deklaracja metody chronionej lub publicznej rozwiązuje problem, ponieważ nie jest to czyste rozwiązanie.

Ligi
źródło
2
Wystąpił podobny problem, ale metoda testowa i pakiet-prywatna były w tym samym pakiecie. Myślę, że być może Mockito ma ogólne problemy z metodami prywatnymi.
Dave
22

W moim przypadku, używając Mockito 2.0, musiałem zmienić wszystkie any()parametry, aby zgasić nullable()prawdziwe połączenie.

ejaenv
źródło
2
Nie pozwól, że 321 głosujących najlepszych odpowiedzi cię zdołuje, to rozwiązało mój problem :) Walczę z tym od kilku godzin!
Chris Kessel
3
To była dla mnie odpowiedź. Aby jeszcze łatwiejsze dla tych, które nastąpi wówczas, gdy wyśmianie metodę składnia jest: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder
Z Mockito 2.23.4 mogę potwierdzić, że nie jest to konieczne, działa dobrze anyi eqdopasowuje.
vmaldosan
2
Próbowałem trzech różnych podejść do wersji 2.23.4 lib: any (), eq () i nullable (). Tylko później działał
ryżman
Cześć, Twoje rozwiązanie jest naprawdę fajne i również dla mnie zadziałało. Dzięki
Dhiren Solanki
16

Odpowiedź Tomasza Nurkiewicza wydaje się nie opowiadać całej historii!

Uwaga: wersja Mockito: 1.10.19.

Jestem bardzo nowicjuszem Mockito, więc nie potrafię wyjaśnić następującego zachowania: jeśli istnieje ekspert, który może poprawić tę odpowiedź, nie krępuj się.

Metoda, o której tu mowa getContentStringValue, NIE jest finali NIE static .

Linia ta ma wywołać oryginalną metodę getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

Ta linia nie wywołuje oryginalnej metody getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

Z powodów, na które nie mogę odpowiedzieć, użycie isA()powoduje, że zamierzone (?) Działanie „nie wywoływać metody” doReturnnie powiodło się.

Spójrzmy na użyte tutaj podpisy metod: obie są staticmetodami Matchers. Obaj mówią Javadoc o powrocie null, co jest trochę trudne do opanowania w sobie. Prawdopodobnie Classobiekt przekazany podczas badania parametru jest badany, ale wynik nigdy nie jest obliczany ani odrzucany. Jeśli się uwzględninull może to oznaczać dowolną klasę i że liczysz na to, że wyśmiewana metoda nie zostanie wywołana, czy podpisy isA( ... )i any( ... )po prostu nie zwrócą się nullzamiast parametru ogólnego * <T>?

Tak czy siak:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

Dokumentacja API nie daje żadnych wskazówek na ten temat. Wydaje się również, że potrzeba takiego zachowania „nie wywoływać metody” jest „bardzo rzadka”. Ja osobiście używam tej techniki przez cały czas : zazwyczaj uważam, że kpina obejmuje kilka linii, które „ustawiają scenę” ... a następnie wywołuje metodę, która następnie „odtwarza” scenę w symulowanym kontekście, który wystawiłeś… Podczas gdy ustawiasz scenerię i rekwizyty, ostatnią rzeczą, jakiej pragniesz, jest, aby aktorzy weszli na scenę po lewej i zaczęli grać swoje serca ...

Ale to znacznie wykracza poza moją pensję ... Zapraszam wyjaśnienia od wszystkich przechodzących arcykapłanów Mockito ...

* czy „parametr ogólny” jest właściwym terminem?

gryzoń mike
źródło
Nie wiem, czy to dodaje przejrzystości, czy też dezorientuje sprawę, ale różnica między isA () i any () polega na tym, że isA faktycznie sprawdza typ, podczas gdy jakakolwiek rodzina metod () została stworzona po prostu, aby uniknąć rzutowania typu argument.
Kevin Welker
@KevinWelker Thanks. I rzeczywiście, w nazwach metod nie brakuje pewnej oczywistej jakości. Robię jednak, i jakkolwiek łagodnie, sprzeciwiam się genialnym projektantom Mockito za to, że nie dokumentowali odpowiednio. Bez wątpienia muszę przeczytać kolejną książkę o Mockito. PS wydaje się, że jest bardzo mało zasobów do nauczania „pośredniego Mockito”!
Mike gryzoni
1
Historia jest taka, że ​​metody anyXX zostały stworzone jako pierwszy sposób na radzenie sobie tylko z rzutowaniem typów. Następnie, gdy zasugerowano, że dodają sprawdzanie argumentów, nie chcieli łamać użytkowników istniejącego interfejsu API, więc stworzyli rodzinę isA (). Wiedząc, że metody any () powinny były cały czas sprawdzać typ, opóźniły ich zmianę, dopóki nie wprowadziły innych przełomowych zmian w przeglądzie Mockito 2.X (których jeszcze nie próbowałem). W wersji 2.x + metody anyX () są aliasami metod isA ().
Kevin Welker
Dziękuję Ci. Jest to kluczowa odpowiedź dla tych z nas, którzy wykonują kilka aktualizacji biblioteki jednocześnie, ponieważ kod, który kiedyś działał, nagle i cicho zawodzi.
Dex Stakker
6

Kolejnym możliwym scenariuszem, który może powodować problemy ze szpiegami, jest testowanie fasoli szparagowej (z ramą testu wiosennego) lub innej struktury, która przybliża obiekty podczas testu .

Przykład

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

W powyższym kodzie zarówno Spring, jak i Mockito będą próbowały proxy twojego obiektu MonitoringDocumentsRepository, ale Spring będzie pierwszy, co spowoduje prawdziwe wywołanie metody findMonitoringDocuments. Jeśli debugujemy nasz kod tuż po umieszczeniu szpiega w obiekcie repozytorium, będzie on wyglądał następująco:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean na ratunek

Jeśli zamiast @Autowiredadnotacji użyjemy @SpyBeanadnotacji, rozwiążemy powyższy problem, adnotacja SpyBean również wstrzykuje obiekt repozytorium, ale najpierw będzie proxy przez Mockito i będzie wyglądać jak ten wewnątrz debuggera

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

a oto kod:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}
Adrian Kapuściński
źródło
1

Znalazłem jeszcze jeden powód, dla którego szpieg wywołuje oryginalną metodę.

Ktoś wpadł na pomysł, aby wyśmiewać finalklasę i dowiedział się o MockMaker:

Ponieważ działa to inaczej niż nasz obecny mechanizm, a ten ma inne ograniczenia i ponieważ chcemy zebrać doświadczenie i opinie użytkowników, ta funkcja musiała zostać wyraźnie aktywowana, aby była dostępna; można to zrobić za pomocą mechanizmu rozszerzenia mockito, tworząc plik src/test/resources/mockito-extensions/org.mockito.plugins.MockMakerzawierający jedną linię:mock-maker-inline

Źródło: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Po scaleniu i przeniesieniu tego pliku na mój komputer moje testy zakończyły się niepowodzeniem.

Musiałem tylko usunąć linię (lub plik) i spy()działałem.

Matruskan
źródło
to był powód w moim przypadku, próbowałem kpić z ostatecznej metody, ale wciąż wywoływała prawdziwą bez wyraźnego komunikatu o błędzie, co było mylące.
Bashar Ali Labadi
1

Trochę późno na imprezę, ale powyższe rozwiązania nie działały dla mnie, więc dzielę się 0,02 $

Wersja Mokcito: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

Poniższe NIE działało dla mnie (wywoływano rzeczywistą metodę):

1.

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2)

doReturn(0).when(spy , "handleAction", any(), anyString());

3)

doReturn(0).when(spy , "handleAction", null, null);

Po DZIAŁANIU:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());
próbujący się uczyć
źródło
0

Jednym ze sposobów upewnienia się, że metoda z klasy nie jest wywoływana, jest przesłonięcie metody za pomocą manekina.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });
Geoffrey Ritchey
źródło
-1

Odpowiedź dla użytkowników Scali: Nawet stawianie na doReturnpierwszym miejscu nie działa! Zobacz ten post .

Nick Resnick
źródło
to nie jest odpowiedź
Umpa