Mockito: Der Versuch, eine Methode auszuspionieren, ruft die ursprüngliche Methode auf


350

Ich benutze Mockito 1.9.0. Ich möchte das Verhalten für eine einzelne Methode einer Klasse in einem JUnit-Test verspotten, also habe ich

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

Das Problem ist, dass in der zweiten Zeile myClassSpy.method1()tatsächlich aufgerufen wird, was zu einer Ausnahme führt. Der einzige Grund, warum ich Mocks verwende, ist, dass später, wann myClassSpy.method1()immer aufgerufen wird, die reale Methode nicht aufgerufen wird und das myResultsObjekt zurückgegeben wird.

MyClassist eine Schnittstelle und myInstanceist eine Implementierung davon, wenn das wichtig ist.

Was muss ich tun, um dieses Spionageverhalten zu korrigieren?


Schauen Sie sich das an: stackoverflow.com/a/29394497/355438
Lu55

Antworten:


609

Lassen Sie mich die offizielle Dokumentation zitieren :

Wichtige Informationen zum Ausspähen realer Objekte!

Manchmal ist es unmöglich, when (Object) zum Stubben von Spionen zu verwenden. Beispiel:

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);

In Ihrem Fall geht es ungefähr so:

doReturn(resulstIWant).when(myClassSpy).method1();

27
Was ist, wenn ich diese Methode verwende und meine ursprüngliche Methode NOCH aufgerufen wird? Könnte es ein Problem mit den von mir übergebenen Parametern geben? Hier ist der ganze Test: pastebin.com/ZieY790P send Methode wird aufgerufen
Evgeni Petrov

26
@EvgeniPetrov Wenn Ihre ursprüngliche Methode noch aufgerufen wird, liegt dies wahrscheinlich daran, dass Ihre ursprüngliche Methode endgültig ist. Mockito verspottet keine endgültigen Methoden und kann Sie nicht vor dem Verspotten endgültiger Methoden warnen.
MarcG

1
ist das auch für doThrow () möglich?
Gobliins

1
Ja, leider sind statische Methoden nicht spottbar und "nicht ausspionierbar". Was ich mit statischen Methoden mache, ist, eine Methode um den statischen Aufruf zu wickeln und für diese Methode doNothing oder doReturn zu verwenden. Mit Singletons oder Scala-Objekten verschiebe ich das Fleisch der Logik in eine abstrakte Klasse, und das gibt mir die Möglichkeit, eine alternative Testklasse für das Objekt zu erstellen, auf dem ich einen Spion erstellen kann.
Andrew Norman

24
Und was ist, wenn die NICHT endgültige und NICHT statische Methode immer noch aufgerufen wird?
X-HuMan

27

Mein Fall war anders als die akzeptierte Antwort. Ich habe versucht, eine paketprivate Methode für eine Instanz zu verspotten, die nicht in diesem Paket enthalten war

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

und die Testklassen

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(){}
}

Die Kompilierung ist korrekt, aber wenn versucht wird, den Test einzurichten, wird stattdessen die reale Methode aufgerufen.

Das Deklarieren der Methode als geschützt oder öffentlich behebt das Problem, obwohl es keine saubere Lösung ist.


2
Ich bin auf ein ähnliches Problem gestoßen, aber der Test und die paketprivate Methode befanden sich im selben Paket. Ich denke, vielleicht hat Mockito generell Probleme mit paketprivaten Methoden.
Dave

22

In meinem Fall musste ich mit Mockito 2.0 alle any()Parameter auf ändern , nullable()um den eigentlichen Aufruf zu stoppen.


2
Lassen Sie sich von dieser 321-Stimmen-Antwort nicht unterkriegen, das hat mein Problem gelöst :) Ich habe seit ein paar Stunden damit zu kämpfen!
Chris Kessel

3
Das war die Antwort für mich. Um es für diejenigen, die folgen, noch einfacher zu machen, wenn Sie sich über Ihre Methode lustig machen, lautet die Syntax: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder

Mit Mockito 2.23.4 kann ich bestätigen, dass dies nicht notwendig ist, es funktioniert gut mit anyund eqMatchern.
Vmaldosan

2
Versuchte drei verschiedene Ansätze in der Version 2.23.4 lib: any (), eq () und nullable (). Nur der spätere arbeitete
Ryzhman

Hallo, deine Lösung war wirklich nett und hat auch für mich funktioniert. Danke
Dhiren Solanki

16

Die Antwort von Tomasz Nurkiewicz scheint nicht die ganze Geschichte zu erzählen!

NB Mockito-Version: 1.10.19.

Ich bin ein Mockito-Neuling und kann daher das folgende Verhalten nicht erklären: Wenn es einen Experten gibt, der diese Antwort verbessern kann, fühlen Sie sich bitte frei.

Die hier fragliche Methode getContentStringValueist NICHT final und NICHT static .

Diese Linie hat die ursprüngliche Methode aufrufen getContentStringValue:

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

Diese Zeile ruft nicht die ursprüngliche Methode auf getContentStringValue:

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

Aus Gründen, die ich nicht beantworten kann, isA()führt die Verwendung dazu , dass das beabsichtigte (?) Verhalten "Methode nicht aufrufen" doReturnfehlschlägt.

Schauen wir uns die hier beteiligten Methodensignaturen an: Sie sind beide staticMethoden von Matchers. Beide werden vom Javadoc zur Rückkehr aufgefordert null, was ein wenig schwierig ist, den Kopf in sich selbst zu bekommen. Vermutlich Classwird das als Parameter übergebene Objekt untersucht, das Ergebnis jedoch nie berechnet oder verworfen. Da nulldies für jede Klasse stehen kann und Sie hoffen, dass die verspottete Methode nicht aufgerufen wird, könnten die Signaturen von isA( ... )und nicht any( ... )nur nulleinen generischen Parameter * zurückgeben <T>?

Wie auch immer:

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

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

Die API-Dokumentation gibt keinen Hinweis darauf. Es scheint auch zu sagen, dass die Notwendigkeit eines solchen Verhaltens "Methode nicht aufrufen" "sehr selten" ist. Persönlich verwende ich diese Technik die ganze Zeit : Normalerweise stelle ich fest, dass das Verspotten einige Zeilen umfasst, die "die Szene setzen" ... gefolgt vom Aufrufen einer Methode, die dann die Szene in dem von Ihnen inszenierten Scheinkontext "ausspielt". . und während Sie die Szenerie und die Requisiten einrichten, ist das Letzte, was Sie wollen, dass die Schauspieler die Bühne links betreten und anfangen, ihre Herzen zu spielen ...

Aber das geht weit über meine Gehaltsstufe hinaus ... Ich lade alle vorbeikommenden Mockito-Hohepriester zu Erklärungen ein ...

* Ist "generischer Parameter" der richtige Begriff?


Ich weiß nicht, ob dies Klarheit schafft oder die Sache weiter verwirrt, aber der Unterschied zwischen isA () und any () besteht darin, dass isA tatsächlich eine Typprüfung durchführt, während eine beliebige () Methodenfamilie nur erstellt wurde, um ein Typ-Casting der zu vermeiden Streit.
Kevin Welker

@ KevinWelker Danke. Und tatsächlich fehlt es den Methodennamen nicht an einer gewissen selbsterklärenden Qualität. Ich habe jedoch Probleme mit den genialen Mockito-Designern, weil sie nicht angemessen dokumentieren. Zweifellos muss ich noch ein Buch über Mockito lesen. PS Tatsächlich scheint es nur sehr wenige Ressourcen zu geben, um "Intermediate Mockito" zu unterrichten!
Mike Nagetier

1
Die Geschichte ist, dass die anyXX-Methoden zuerst erstellt wurden, um nur mit Typecasting umzugehen. Als dann vorgeschlagen wurde, die Argumentprüfung hinzuzufügen, wollten sie die Benutzer der vorhandenen API nicht trennen, und erstellten die isA () -Familie. Da sie wussten, dass die any () -Methoden die Typprüfung die ganze Zeit über hätten durchführen sollen, verzögerten sie das Ändern dieser Methoden, bis sie andere wichtige Änderungen in der Mockito 2.X-Überholung einführten (die ich noch nicht ausprobiert habe). In 2.x + sind anyX () -Methoden Aliase für die isA () -Methoden.
Kevin Welker

Vielen Dank. Dies ist eine entscheidende Antwort für diejenigen von uns, die mehrere Bibliotheksaktualisierungen gleichzeitig durchführen, da der früher ausgeführte Code plötzlich und stillschweigend fehlschlägt.
Dex Stakker

5

Ein weiteres mögliches Szenario, das Probleme mit Spionen verursachen kann, ist das Testen von Spring Beans (mit Spring Test Framework) oder eines anderen Frameworks, das Ihre Objekte während des Tests als Proxy verwendet .

Beispiel

@Autowired
private MonitoringDocumentsRepository repository

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

Im obigen Code versuchen sowohl Spring als auch Mockito, Ihr MonitoringDocumentsRepository-Objekt zu vertreten, aber Spring ist das erste, was einen echten Aufruf der findMonitoringDocuments-Methode verursacht. Wenn wir unseren Code direkt nach dem Aussetzen eines Spions auf ein Repository-Objekt debuggen, sieht es im Debugger folgendermaßen aus:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean zur Rettung

Wenn @Autowiredwir stattdessen eine Annotation verwenden @SpyBean, lösen wir das obige Problem. Die SpyBean-Annotation fügt auch ein Repository-Objekt ein, wird jedoch zuerst von Mockito als Proxy verwendet und sieht im Debugger so aus

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

und hier ist der Code:

@SpyBean
private MonitoringDocumentsRepository repository

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

1

Ich habe noch einen weiteren Grund für Spion gefunden, die ursprüngliche Methode aufzurufen.

Jemand hatte die Idee, eine finalKlasse zu verspotten , und fand heraus MockMaker:

Da dies anders funktioniert als unser aktueller Mechanismus und dieser unterschiedliche Einschränkungen aufweist und wir Erfahrungen und Benutzerfeedback sammeln möchten, musste diese Funktion explizit aktiviert werden, um verfügbar zu sein. Dies kann über den Mockito-Erweiterungsmechanismus erfolgen, indem eine Datei src/test/resources/mockito-extensions/org.mockito.plugins.MockMakermit einer einzelnen Zeile erstellt wird:mock-maker-inline

Quelle: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Nachdem ich diese Datei zusammengeführt und auf meinen Computer gebracht hatte, schlugen meine Tests fehl.

Ich musste nur die Zeile (oder die Datei) entfernen und spy()arbeitete.


Dies war der Grund in meinem Fall, ich habe versucht, eine endgültige Methode zu verspotten, aber sie hat immer wieder die echte aufgerufen, ohne eine klare Fehlermeldung, die verwirrend war.
Bashar Ali Labadi

1

Etwas spät zur Party, aber die oben genannten Lösungen haben bei mir nicht funktioniert, also teile ich meine 0,02 $

Mokcito-Version: 1.10.19

MyClass.java

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

Test.java

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

Folgendes hat bei mir NICHT funktioniert (die eigentliche Methode wurde aufgerufen):

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);

Folgende ARBEITEN:

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

0

Eine Möglichkeit, um sicherzustellen, dass eine Methode aus einer Klasse nicht aufgerufen wird, besteht darin, die Methode mit einem Dummy zu überschreiben.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.