wake-up-neo.com

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

Ich verwende Mockito 1.9.0. Ich möchte das Verhalten für eine einzelne Methode einer Klasse in einem JUnit-Test nachahmen, so habe ich es getan

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

In der zweiten Zeile wird myClassSpy.method1() tatsächlich aufgerufen, was zu einer Ausnahme führt. Der einzige Grund, warum ich Mocks verwende, ist der, dass später beim Aufruf von myClassSpy.method1() die echte Methode nicht aufgerufen wird und das myResults-Objekt zurückgegeben wird. 

MyClass ist eine Schnittstelle und myInstance ist eine Implementierung davon, wenn dies wichtig ist.

Was muss ich tun, um dieses Spionageverhalten zu korrigieren?

256
Dave

Lassen Sie mich die offizielle Dokumentation zitieren :

Wichtiger Hinweis auf das Ausspähen realer Objekte!

Manchmal ist es nicht möglich, mit (Object) Spione zu spucken. 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();
461

Mein Fall unterschied sich von der akzeptierten Antwort. Ich habe versucht, eine private Paketmethode für eine Instanz zu verspotten, die nicht in diesem Paket enthalten ist

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 beim Versuch, den Test einzurichten, wird stattdessen die echte Methode aufgerufen.

Das Deklarieren der Methode protected oder public behebt das Problem, da dies keine saubere Lösung ist.

24
Maragues

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

Anmerkung: Mockito-Version: 1.10.19.

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

Die fragliche Methode, getContentStringValue, ist NICHTfinal und NICHTstatic.

Diese Zeile ruft die ursprüngliche Methode getContentStringValue auf:

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

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

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

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

Betrachten wir die hier verwendeten Methodensignaturen: Beide sind static-Methoden von Matchers. Beide sagen im Javadoc, null zurückzugeben, was ein wenig schwierig ist, den Kopf in sich selbst zu bekommen. Vermutlich wird das als Parameter übergebene Class-Objekt untersucht, das Ergebnis wird jedoch nie berechnet oder verworfen. In Anbetracht der Tatsache, dass null für jede Klasse stehen kann und Sie hoffen, dass die gespielte Methode nicht aufgerufen wird, könnten die Signaturen von isA( ... ) und any( ... ) nicht null statt eines generischen Parameters * <T> zurückgeben.

Sowieso:

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

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

Die API-Dokumentation gibt dazu keinen Hinweis. 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 finde ich, dass das Spotteln ein paar Zeilen umfasst, die "die Szene setzen" ... gefolgt von dem Aufruf einer Methode, die die Szene dann in dem von Ihnen gespielten Schein "ausspielt" haben inszeniert ... und während Sie die Szenerie und die Requisiten aufstellen, ist das letzte, was Sie wollen, dass die Schauspieler die Bühne verlassen und beginnen, ihre Herzen zu spielen ...

Aber das ist weit über meine Gehaltsstufe hinaus ... Ich lade Erklärungen von vorbeikommenden Mockito-Hohenpriestern ein ...

* Ist "generischer Parameter" der richtige Begriff?

14
mike rodent

In meinem Fall musste ich bei Verwendung von Mockito 2.0 alle Parameter any () in nullable () ändern, um den eigentlichen Aufruf zu unterdrücken.

12
ejaenv

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 annähert .

Beispiel

@Autowired
private MonitoringDocumentsRepository repository

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

In obigem Code versuchen Spring und Mockito, Ihr MonitoringDocumentsRepository-Objekt zu repräsentieren, aber Spring wird an erster Stelle stehen, was einen echten Aufruf der findMonitoringDocuments-Methode verursacht. Wenn wir unseren Code direkt nach dem Ablegen eines Spions auf das Repository-Objekt debuggen, sieht das in Debugger folgendermaßen aus:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean zur Rettung

Wenn statt @Autowired-Annotation @SpyBean-Annotation verwendet wird, lösen wir das obige Problem. Die SpyBean-Annotation fügt auch ein Repository-Objekt hinzu. Dieses Objekt wird jedoch zunächst von Mockito als Proxyserver angezeigt und im Debugger so aussehen

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 einen weiteren Grund gefunden, warum der Spion die ursprüngliche Methode aufgerufen hat.

Jemand hatte die Idee, eine final-Klasse zu verspotten, und fand MockMaker:

Da dies anders funktioniert als bei unserem derzeitigen Mechanismus, und dieser hat andere Einschränkungen. Da wir Erfahrung und Rückmeldungen von Benutzern sammeln möchten, musste diese Funktion explizit aktiviert werden, um verfügbar zu sein. Dies kann über den mockito-Erweiterungsmechanismus erfolgen, indem die Datei src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker mit 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-classemethods

Nachdem ich diese Datei zusammengeführt und auf meinen Rechner gebracht habe, sind meine Tests fehlgeschlagen.

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

0
Matruskan

Antwort für Scala-Benutzer: Selbst wenn doReturn an erster Stelle steht, funktioniert das nicht! Siehe diesen Beitrag .

0
Nick Resnick

Um sicherzustellen, dass eine Methode aus einer Klasse nicht aufgerufen wird, können Sie die Methode mit einem Dummy überschreiben. 

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