Mockito - @Spy vs @Mock

95

Mockito - Ich verstehe, dass ein Spion die realen Methoden für ein Objekt aufruft, während ein Mock Methoden für das Doppelobjekt aufruft. Auch Spione sind zu vermeiden, es sei denn, es riecht nach Code. Wie funktionieren Spione und wann sollte ich sie tatsächlich benutzen? Wie unterscheiden sie sich von Mocks?

Abhinav
quelle
2
mögliches Duplikat von mockito mock vs. spy
rds
Mögliches Duplikat von Mocking vs. Spying in spöttischen Frameworks
PenguinEngineer

Antworten:

88

Technisch gesehen sind sowohl "Mocks" als auch "Spies" eine besondere Art von "Test-Doubles".

Mockito macht die Unterscheidung leider komisch.

Ein Mock in Mockito ist ein normales Mock in anderen Mocking-Frameworks (ermöglicht es Ihnen, Aufrufe zu stubben, dh bestimmte Werte aus Methodenaufrufen zurückzugeben).

Ein Spion in Mockito ist ein Teil-Mock in anderen Mocking-Frameworks (ein Teil des Objekts wird verspottet und ein Teil verwendet echte Methodenaufrufe).

Crazyjavahacking
quelle
35

Beide können verwendet werden, um Methoden oder Felder zu verspotten. Der Unterschied besteht darin, dass Sie im Mock ein vollständiges Mock- oder Fake-Objekt erstellen, während Sie im Spion das reale Objekt haben und nur bestimmte Methoden ausspionieren oder stubben.

Da es sich bei Spionageobjekten natürlich um eine echte Methode handelt, wird das echte Methodenverhalten aufgerufen, wenn Sie die Methode nicht stubben. Wenn Sie die Methode ändern und verspotten möchten, müssen Sie sie stubben.

Betrachten Sie das folgende Beispiel als Vergleich.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

Wann solltest du Mock oder Spion benutzen? Wenn Sie sicher sein und vermeiden möchten, externe Dienste anzurufen, und nur die Logik im Gerät testen möchten, verwenden Sie mock. Wenn Sie einen externen Dienst aufrufen und einen Aufruf mit echten Abhängigkeiten ausführen oder einfach sagen möchten, dass Sie das Programm so ausführen möchten, wie es ist, und nur bestimmte Methoden stubben möchten, verwenden Sie spy. Das ist also der Unterschied zwischen Spion und Mock in Mockito.

Vu Truong
quelle
Gute Antwort, aber es wird verify () auf einen Mock-Only-Fehler werfen und die Tests nicht ausführen, es sei denn, Sie initialisieren Ihre Listen in der @ Before setUp () -Methode wie hier mockList = mock (ArrayList.class); spyList = spy (ArrayList.class); und entfernen Sie die hier vorgeschlagene Schein- und Spionage-Annotation. Ich habe es getestet und meine Tests bestehen jetzt.
The_Martian
17

TL; DR-Version,

Mit mock wird eine Bare-Bone-Shell-Instanz für Sie erstellt.

List<String> mockList = Mockito.mock(ArrayList.class);

Mit Spy können Sie eine vorhandene Instanz teilweise verspotten

List<String> spyList = Mockito.spy(new ArrayList<String>());

Typischer Anwendungsfall für Spy: Die Klasse verfügt über einen parametrisierten Konstruktor. Sie möchten zuerst das Objekt erstellen.

del bao
quelle
14

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

Ich kopiere etwas davon 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 sich nur über DepositMoneyService und WithdrawMoneyService lustig machen können.

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

    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
13

Der beste Ausgangspunkt sind wahrscheinlich die Dokumente für Mockito .

Generell können Sie mit dem Mockito-Mock Stubs erstellen.

Sie würden eine Stub-Methode erstellen, wenn diese Methode beispielsweise eine teure Operation ausführt. Angenommen, es wird eine Datenbankverbindung hergestellt, ein Wert aus der Datenbank abgerufen und an den Aufrufer zurückgegeben. Das Herstellen der Datenbankverbindung kann 30 Sekunden dauern und die Testausführung bis zu dem Punkt verlangsamen, an dem Sie wahrscheinlich den Kontext wechseln (oder den Test nicht mehr ausführen).

Wenn sich die zu testende Logik nicht um die Datenbankverbindung kümmert, können Sie diese Methode durch einen Stub ersetzen, der einen fest codierten Wert zurückgibt.

Mit dem Mockito-Spion können Sie überprüfen, ob eine Methode andere Methoden aufruft. Dies kann sehr nützlich sein, wenn Sie versuchen, Legacy-Code zu testen.

Es ist nützlich, wenn Sie eine Methode testen, die Nebenwirkungen wirkt, dann würden Sie einen Mockito-Spion verwenden. Dadurch werden Aufrufe an das reale Objekt delegiert und Sie können den Methodenaufruf, die Häufigkeit des Aufrufs usw. überprüfen.

Jaimie Whiteside
quelle
6

Ich mag die Einfachheit dieser Empfehlung:

  • Wenn Sie sicher sein und vermeiden möchten, externe Dienste anzurufen, und nur die Logik im Gerät testen möchten, verwenden Sie mock .
  • Wenn Sie einen externen Dienst aufrufen und echte Abhängigkeiten aufrufen möchten oder einfach sagen möchten, dass Sie das Programm so ausführen möchten, wie es ist, und nur bestimmte Methoden stubben möchten, verwenden Sie spy .

Quelle: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Ein gemeinsamer Unterschied ist:

  • Wenn Sie die Methode (n) einer Abhängigkeit direkt stubben möchten, verspotten Sie diese Abhängigkeit.
  • Wenn Sie die Daten in einer Abhängigkeit stubben möchten, damit alle Methoden die benötigten Testwerte zurückgeben, spionieren Sie diese Abhängigkeit aus.
leo9r
quelle
Beachten Sie, dass Spy und Mock immer für Abhängigkeiten gedacht sind und nicht für das zu testende System.
Leo9r
6

Zusamenfassend:

@Spyund @Mockwerden häufig beim Testen von Code verwendet, aber Entwickler verwirren in Fällen, in denen sie einen von ihnen verwenden müssen, und daher verwenden Entwickler letztendlich @Mock, um sicher zu sein.

  • Verwenden @MockSie diese Option, wenn Sie die Funktionalität nur extern testen möchten ohne diese Methode tatsächlich aufzurufen.
  • Verwenden @SpySie diese Option, wenn Sie die Funktionalität extern + intern mit der aufgerufenen Methode testen möchten .

Unten ist das Beispiel, in dem ich das Szenario von Election20xx in Amerika aufgenommen habe.

Die Wähler können nach VotersOfBelow21und unterteilt werden VotersOfABove21.

Die ideale Exit - Umfrage sagt , dass Trump die Wahl gewinnen wird , da VotersOfBelow21und VotersOfABove21beide stimmen für Trumpf sagen : „ Wir gewählte Präsident Trump

Dies ist jedoch nicht das eigentliche Szenario:

Wähler beider Altersgruppen stimmten für Trump, weil sie keine andere effektive Wahl hatten als Herrn Trump.

Wie testest du es?

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

Beachten Sie nun, dass in den ersten beiden Klassen beide Altersgruppen sagen, dass sie keine bessere Wahl haben als Trump. Was ausdrücklich bedeutet, dass sie für Trump gestimmt haben, nur weil sie keine Wahl hatten.

Jetzt ElectionOfYear20XX sagt der , dass Trump gewonnen hat, weil beide Altersgruppen ihn mit überwältigender Mehrheit gewählt haben.

Wenn wir das testen würden ElectionOfYear20XX mit @Mock testen würden, könnten wir möglicherweise nicht den wahren Grund herausfinden, warum Trump gewonnen hat. Wir werden nur den externen Grund testen.

Wenn wir das ElectionOfYear20XXmit @Spy testen, erhalten wir den wahren Grund, warum Trump mit den Ergebnissen der externen Exit-Umfrage gewonnen hat, dh intern + extern.


Unsere ELectionOfYear20XX_TestKlasse:

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Dies sollte nur die logischen Testergebnisse ausgeben, dh externe Prüfung:

We elected President Trump 
We elected President Trump 

Testen sowohl @Spyextern als auch intern mit tatsächlichem Methodenaufruf.

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Ausgabe:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 
Vishwa Ratna
quelle