Was ist der Unterschied zwischen Spott und Spionage bei der Verwendung von Mockito?

137

Was wäre ein Anwendungsfall für die Verwendung eines Mockito-Spions?

Es scheint mir, dass jeder Spionage-Anwendungsfall mit callRealMethod mit einem Mock behandelt werden kann.

Ein Unterschied, den ich sehen kann, ist, wenn Sie möchten, dass die meisten Methodenaufrufe real sind, werden einige Codezeilen gespeichert, um einen Schein gegen einen Spion zu verwenden. Ist es das oder fehlt mir das Gesamtbild?

Victor Grazi
quelle

Antworten:

100

Die Antwort finden Sie in der Dokumentation :

Echte Teilspottungen (seit 1.8.0)

Nach vielen internen Debatten und Diskussionen auf der Mailingliste wurde Mockito schließlich eine teilweise Scheinunterstützung hinzugefügt. Bisher haben wir Partial Mocks als Code-Gerüche betrachtet. Wir haben jedoch einen legitimen Anwendungsfall für partielle Verspottungen gefunden.

Vor Release 1.8 produzierte spy () keine echten Partial Mocks und es war für einige Benutzer verwirrend. Lesen Sie mehr über Spionage: hier oder in javadoc für die Spionagemethode (Objekt).

callRealMethod()wurde nach eingeführt spy(), aber spy () wurde natürlich dort gelassen, um die Abwärtskompatibilität sicherzustellen.

Ansonsten haben Sie Recht: Alle Methoden eines Spions sind real, sofern sie nicht gestoppt werden. Alle Methoden eines Mocks werden gestoppt, sofern sie nicht callRealMethod()aufgerufen werden. Im Allgemeinen würde ich es vorziehen, zu verwenden callRealMethod(), da es mich nicht zwingt, die doXxx().when()Redewendung anstelle der traditionellen zu verwendenwhen().thenXxx()

JB Nizet
quelle
Das Problem bei der Bevorzugung von Mock gegenüber Spion besteht in diesen Fällen darin, dass die Klasse ein Mitglied verwendet, das nicht in sie injiziert (sondern lokal initialisiert) ist und später von der "echten" Methode verwendet wird. Im Mock wird das Mitglied auf seinen Standard-Java-Wert initialisiert, was zu falschem Verhalten oder sogar einer NullPointerException führen kann. Der Weg, dies zu übergeben, besteht darin, eine "init" -Methode hinzuzufügen und sie dann "wirklich" aufzurufen, aber das scheint mir ein bisschen übertrieben zu sein.
Eyal Roth
Aus dem Dokument: "Spione sollten sorgfältig und gelegentlich eingesetzt werden, beispielsweise beim Umgang mit Legacy-Code." Der Einheitentestraum leidet unter zu vielen Möglichkeiten, dasselbe zu tun.
GDBJ
89

Unterschied zwischen einem Spion und einem Mock

Wenn Mockito ein Modell erstellt, geschieht dies aus der Klasse eines Typs und nicht aus einer tatsächlichen Instanz. Der Mock erstellt einfach eine Bare-Bones-Shell-Instanz der Klasse, die vollständig instrumentiert ist, um Interaktionen mit ihr zu verfolgen. Auf der anderen Seite wird der Spion eine vorhandene Instanz umschließen. Es verhält sich immer noch genauso wie die normale Instanz - der einzige Unterschied besteht darin, dass es auch instrumentiert wird, um alle Interaktionen mit ihm zu verfolgen.

Im folgenden Beispiel erstellen wir ein Modell der ArrayList-Klasse:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Wie Sie sehen können - das Hinzufügen eines Elements zur verspotteten Liste fügt eigentlich nichts hinzu - ruft die Methode nur ohne weitere Nebenwirkungen auf. Ein Spion hingegen verhält sich anders - er ruft tatsächlich die eigentliche Implementierung der Methode add auf und fügt das Element der zugrunde liegenden Liste hinzu:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Hier können wir sicherlich sagen, dass die reale interne Methode des Objekts aufgerufen wurde, denn wenn Sie die size () -Methode aufrufen, erhalten Sie die Größe als 1, aber diese size () -Methode wurde nicht verspottet! Woher komme ich? Die interne Methode real size () wird aufgerufen, da size () nicht verspottet (oder gestoppt) wird. Daher können wir sagen, dass der Eintrag dem realen Objekt hinzugefügt wurde.

Quelle: http://www.baeldung.com/mockito-spy + Selbstnotizen.

Saurabh Patil
quelle
1
Meinst du nicht, dass size () 1 zurückgibt?
schwarz
Warum wird im ersten Beispiel mockedList.size()zurückgegeben, 0wenn diese Methode auch nicht ausgeblendet wurde? Ist das nur ein Standardwert angesichts des Rückgabetyps der Methode?
Mike
@mike: Gibt mockedList.size()einen zurück intund der Standardwert von intist in Java 0. Wenn Sie versuchen, assertEquals(0, mockedList.size());danach auszuführen mockedList.clear();, bleibt das Ergebnis gleich.
RealPK
2
Diese Antwort ist gut und einfach geschrieben und hat mir geholfen, endlich den Unterschied zwischen Schein und Spion zu verstehen. Schön.
PesaDer
38

Wenn es ein Objekt mit 8 Methoden gibt und Sie einen Test haben, bei dem Sie 7 echte Methoden aufrufen und eine Methode stubben möchten, haben Sie zwei Möglichkeiten:

  1. Mit einem Mock müssten Sie es einrichten, indem Sie 7 callRealMethod aufrufen und eine Methode stubben
  2. Mit a müssen spySie es einrichten, indem Sie eine Methode stubben

Die offizielle Dokumentation über doCallRealMethodempfiehlt einen Spion für die partielle Mocks verwenden.

Siehe auch javadoc spy (Object), um mehr über partielle Verspottungen zu erfahren. Mockito.spy () ist eine empfohlene Methode zum Erstellen von Teil-Mocks. Der Grund dafür ist, dass garantiert wird, dass echte Methoden für korrekt erstellte Objekte aufgerufen werden, da Sie für die Erstellung des an die spy () -Methode übergebenen Objekts verantwortlich sind.

user2412398
quelle
5

Spy kann nützlich sein, wenn Sie Komponententests für Legacy-Code erstellen möchten .

Ich habe hier ein ausführbares Beispiel erstellt: https://www.surasint.com/mockito-with-spy/ . Einige davon kopiere ich hier.

Wenn Sie so etwas wie diesen Code haben:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Möglicherweise benötigen Sie keinen Spion, da Sie nur DepositMoneyService und WithdrawMoneyService verspotten können.

Bei einigen Legacy-Codes ist die Abhängigkeit jedoch wie folgt im Code:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Ja, Sie können zum ersten Code wechseln, aber dann wird die API geändert. Wenn diese Methode von vielen Orten verwendet wird, müssen Sie alle ändern.

Alternativ können Sie die Abhängigkeit folgendermaßen extrahieren:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Dann können Sie den Spion verwenden, um die Abhängigkeit wie folgt zu injizieren:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

Weitere Details im obigen Link.

Surasin Tancharoen
quelle
0

Mockist ein nacktes Doppelobjekt. Dieses Objekt hat die gleichen Methodensignaturen, aber die Realisierung ist leer und gibt den Standardwert 0 und null zurück

Spyist ein geklontes Doppelobjekt. Neues Objekt wird basierend auf einem realen Objekt geklont, aber Sie haben die Möglichkeit, es zu verspotten

class A {

    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() {
        foo4();
    }

    void foo4() {

    }
}
@Test
public void testMockA() {

    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

[Doppeltypen testen]

yoAlex5
quelle