Spy in Mockito

JavaJavaBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Mockito ist ein leistungsstarkes Java-Framework, das für das Mocking und Unit-Testing von Java-Anwendungen verwendet wird. In diesem Lab werden wir das Konzept von Spies in Mockito untersuchen. Ein Spy ist ein spezielles Testwerkzeug, das als partieller Mock fungiert - es verfolgt die Interaktionen mit einem Objekt und lässt gleichzeitig die echten Methoden aufgerufen werden.

Dieses doppelte Verhalten macht Spies besonders nützlich, wenn wir die Interaktionen mit Objekten überprüfen möchten, aber dennoch die echte Funktionalität ausführen lassen müssen. In diesem gesamten Lab werden wir lernen, wie man Spies mit verschiedenen Ansätzen erstellt, wie man das Verhalten eines Spies stubbt und wie man Spies mit Mocks vergleicht, um ihre wichtigsten Unterschiede hervorzuheben.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("Java")) -.-> java/DataStructuresGroup(["Data Structures"]) java(("Java")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["Object-Oriented and Advanced Concepts"]) java(("Java")) -.-> java/StringManipulationGroup(["String Manipulation"]) java/StringManipulationGroup -.-> java/strings("Strings") java/StringManipulationGroup -.-> java/stringbuffer_stringbuilder("StringBuffer/StringBuilder") java/DataStructuresGroup -.-> java/arrays("Arrays") java/DataStructuresGroup -.-> java/arrays_methods("Arrays Methods") java/DataStructuresGroup -.-> java/collections_methods("Collections Methods") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("Classes/Objects") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/arraylist("ArrayList") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/annotation("Annotation") subgraph Lab Skills java/strings -.-> lab-117989{{"Spy in Mockito"}} java/stringbuffer_stringbuilder -.-> lab-117989{{"Spy in Mockito"}} java/arrays -.-> lab-117989{{"Spy in Mockito"}} java/arrays_methods -.-> lab-117989{{"Spy in Mockito"}} java/collections_methods -.-> lab-117989{{"Spy in Mockito"}} java/classes_objects -.-> lab-117989{{"Spy in Mockito"}} java/arraylist -.-> lab-117989{{"Spy in Mockito"}} java/annotation -.-> lab-117989{{"Spy in Mockito"}} end

Erstellen eines Java-Projekts und Verständnis von Mockito Spy

In diesem Schritt werden wir unsere Projektstruktur einrichten und verstehen, was ein Spy in Mockito ist. Spies ermöglichen es uns, Methodenaufrufe an echten Objekten zu verfolgen, während wir gleichzeitig ihre tatsächliche Implementierung nutzen.

Erstellen der Java-Datei

Lassen Sie uns zunächst unsere Haupt-Java-Datei für dieses Lab erstellen. Im WebIDE erstellen Sie eine neue Datei namens MockitoSpyDemo.java im Verzeichnis ~/project.

Create a new file

Fügen Sie den folgenden Code hinzu, um die erforderlichen Abhängigkeiten zu importieren:

import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.MockitoAnnotations;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;

public class MockitoSpyDemo {

    public static void main(String[] args) {
        System.out.println("This is a Mockito Spy demonstration.");
        System.out.println("To run the tests, you need to use JUnit.");
    }
}

Verständnis von Mockito Spy

Ein Spy in Mockito ist eine Umhüllung (Wrapper) um ein echtes Objekt, die es Ihnen ermöglicht:

  1. Alle Interaktionen mit dem Objekt zu verfolgen (wie bei einem Mock)
  2. Die echten Methoden des Objekts auszuführen (im Gegensatz zu einem Mock)

Stellen Sie sich einen Spy als einen Beobachter vor, der beobachtet, was mit einem Objekt passiert, aber nicht in sein normales Verhalten eingreift, es sei denn, er ist speziell dazu programmiert.

Lassen Sie uns unsere erste Testmethode hinzufügen, um die grundlegende Verwendung eines Spies zu demonstrieren. Fügen Sie die folgende Methode zu Ihrer MockitoSpyDemo-Klasse hinzu:

@Test
public void basicSpyExample() {
    // Create a list and spy on it
    List<String> realList = new ArrayList<>();
    List<String> spyList = Mockito.spy(realList);

    // Using the spy to add an item (the real method is called)
    spyList.add("Hello");

    // Verify the interaction happened
    Mockito.verify(spyList).add("Hello");

    // The real method was called, so the list actually has the item
    assertEquals(1, spyList.size());
    assertEquals("Hello", spyList.get(0));

    System.out.println("Basic Spy Example - Spy List Content: " + spyList);
    System.out.println("Basic Spy Example - Spy List Size: " + spyList.size());
}

Lassen Sie uns unseren Code kompilieren und ausführen, um zu sehen, ob er funktioniert:

cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java
java -cp .:lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo
Run the code

Sie sollten die folgende Ausgabe sehen:

This is a Mockito Spy demonstration.
To run the tests, you need to use JUnit.

Unsere Hauptmethode wird korrekt ausgeführt. Um jedoch die Tests auszuführen, müssen wir JUnit verwenden. Anstatt für dieses Lab einen komplexen JUnit-Runner einzurichten, werden wir uns auf das Verständnis der Code-Struktur und der Konzepte konzentrieren.

Im nächsten Schritt werden wir verschiedene Möglichkeiten untersuchen, Spies in Mockito zu erstellen.

Verschiedene Möglichkeiten, Spies in Mockito zu erstellen

In diesem Schritt werden wir verschiedene Ansätze untersuchen, um Spies in Mockito zu erstellen. Wir werden lernen, wie man Spies mit der Methode Mockito.spy() und der Annotation @Spy erstellt.

Verwendung der Methode Mockito.spy()

Der direkteste Weg, einen Spy zu erstellen, ist die Verwendung der Methode Mockito.spy(). Fügen wir eine Testmethode hinzu, die diesen Ansatz demonstriert:

@Test
public void testSpyWithMockitoSpyMethod() {
    // Create an ArrayList and a spy of it
    ArrayList<Integer> realList = new ArrayList<>();
    ArrayList<Integer> spyList = Mockito.spy(realList);

    // Adding elements to the spy (real methods are called)
    spyList.add(5);
    spyList.add(10);
    spyList.add(15);

    // Verifying interactions
    Mockito.verify(spyList).add(5);
    Mockito.verify(spyList).add(10);
    Mockito.verify(spyList).add(15);

    // Verifying that elements were actually added to the list
    assertEquals(3, spyList.size());

    System.out.println("Spy Method Example - Spy List Content: " + spyList);
    System.out.println("Spy Method Example - Spy List Size: " + spyList.size());
}

Verwendung der Annotation @Spy

Eine andere Möglichkeit, einen Spy zu erstellen, ist die Verwendung der Annotation @Spy. Dieser Ansatz ist besonders nützlich, wenn Sie Spies automatisch initialisieren möchten. Fügen Sie den folgenden Code zu Ihrer MockitoSpyDemo-Klasse hinzu:

// Declare a spy using annotation
@Spy
ArrayList<Integer> annotationSpyList = new ArrayList<>();

@Before
public void initSpies() {
    // Initialize mocks and spies with annotations
    MockitoAnnotations.initMocks(this);
}

@Test
public void testSpyWithAnnotation() {
    // Adding elements to the spy
    annotationSpyList.add(5);
    annotationSpyList.add(10);
    annotationSpyList.add(15);

    // Verifying interactions
    Mockito.verify(annotationSpyList).add(5);
    Mockito.verify(annotationSpyList).add(10);
    Mockito.verify(annotationSpyList).add(15);

    // Verifying that elements were actually added to the list
    assertEquals(3, annotationSpyList.size());

    System.out.println("Annotation Spy Example - Spy List Content: " + annotationSpyList);
    System.out.println("Annotation Spy Example - Spy List Size: " + annotationSpyList.size());
}

Verständnis des Unterschieds zwischen den Ansätzen

Beide Ansätze erstellen einen Spy, aber sie haben unterschiedliche Anwendungsfälle:

  1. Mockito.spy() ist flexibler und kann überall in Ihren Testmethoden verwendet werden.
  2. Die Annotation @Spy ist praktischer, wenn Sie viele Spies haben und sie alle auf einmal initialisieren möchten.

Die Methode MockitoAnnotations.initMocks(this) ist für die Initialisierung aller annotierten Felder verantwortlich. In realen Projekten könnten Sie @RunWith(MockitoJUnitRunner.class) verwenden, um diese Initialisierung automatisch durchführen zu lassen.

Lassen Sie uns unseren aktualisierten Code kompilieren:

cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java

Der Code sollte ohne Fehler kompilieren. Unsere Testmethoden sind jetzt bereit, aber wie bereits erwähnt, bräuchten wir einen JUnit-Runner, um sie automatisch auszuführen.

Im nächsten Schritt werden wir lernen, wie man Spies stubbt, um ihr Standardverhalten zu überschreiben.

Stubbing von Spies und Behandlung von Ausnahmen

In diesem Schritt werden wir lernen, wie man einen Spy stubbt, um sein Standardverhalten zu überschreiben, und verstehen, wie man Ausnahmen behandelt, die beim Verwenden von Mockito auftreten können.

Stubbing eines Spies

Spies verwenden standardmäßig die echten Methoden eines Objekts, aber manchmal möchten wir dieses Verhalten aus Testzwecken ändern. Dies wird "Stubbing" des Spies genannt. Fügen wir eine Testmethode hinzu, die zeigt, wie man einen Spy stubbt:

@Test
public void testStubbingSpy() {
    // Create a spy of ArrayList
    ArrayList<Integer> spyList = Mockito.spy(new ArrayList<>());

    // Add an element
    spyList.add(5);

    // Verify the element was added
    Mockito.verify(spyList).add(5);

    // By default, contains() should return true for element 5
    assertTrue(spyList.contains(5));
    System.out.println("Stubbing Example - Default behavior: spyList.contains(5) = " + spyList.contains(5));

    // Stub the contains() method to always return false for the value 5
    Mockito.doReturn(false).when(spyList).contains(5);

    // Now contains() should return false, even though the element is in the list
    assertFalse(spyList.contains(5));
    System.out.println("Stubbing Example - After stubbing: spyList.contains(5) = " + spyList.contains(5));

    // The element is still in the list (stubbing didn't change the list content)
    assertEquals(1, spyList.size());
    assertEquals(Integer.valueOf(5), spyList.get(0));
    System.out.println("Stubbing Example - Spy List Content: " + spyList);
}

Wichtige Anmerkung zum Stubbing

Beim Stubbing von Spy-Methoden wird empfohlen, doReturn(), doThrow(), doAnswer() usw. anstelle von when() zu verwenden. Dies liegt daran, dass bei Spies die Syntax when().thenReturn() möglicherweise die echte Methode während des Stubbing aufruft, was unerwartete Nebeneffekte verursachen kann.

Beispiel:

// This might call the real get() method, causing issues if index 10 doesn't exist
Mockito.when(spyList.get(10)).thenReturn(99); // May throw IndexOutOfBoundsException

// This is the correct way to stub a spy
Mockito.doReturn(99).when(spyList).get(10); // Safe, doesn't call the real method

Behandlung der NotAMockException

Wenn Sie versuchen, Mockitos Überprüfungsmethoden auf ein normales Objekt (kein Mock oder Spy) anzuwenden, erhalten Sie eine NotAMockException. Fügen wir eine Testmethode hinzu, um diese Ausnahme zu demonstrieren:

@Test
public void testNotAMockException() {
    try {
        // Create a regular ArrayList (not a mock or spy)
        ArrayList<Integer> regularList = new ArrayList<>();
        regularList.add(5);

        // Try to verify an interaction on a regular object
        Mockito.verify(regularList).add(5);

        fail("Expected NotAMockException was not thrown");
    } catch (NotAMockException e) {
        // Expected exception
        System.out.println("NotAMockException Example - Caught expected exception: " + e.getMessage());
        assertTrue(e.getMessage().contains("Argument passed to verify() is not a mock"));
    }
}

Lassen Sie uns unseren aktualisierten Code kompilieren:

cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java

Der Code sollte ohne Fehler kompilieren. In diesem Schritt haben wir gelernt, wie man Spy-Methoden stubbt, um ihr Standardverhalten zu überschreiben, und wie man die NotAMockException behandelt, die auftritt, wenn man Überprüfungsmethoden auf normale Objekte anwendet.

Im nächsten Schritt werden wir Mocks und Spies vergleichen, um ihre wesentlichen Unterschiede zu verstehen.

Mock vs. Spy: Unterschiede verstehen

In diesem Schritt werden wir direkt Mocks und Spies vergleichen, um ihre wesentlichen Unterschiede zu verstehen. Dieser Vergleich wird Ihnen helfen, das richtige Werkzeug für Ihre Testanforderungen auszuwählen.

Erstellen eines Vergleichstests

Fügen wir eine Testmethode hinzu, die die Unterschiede zwischen Mocks und Spies demonstriert:

@Test
public void testMockVsSpyDifference() {
    // Create a mock and a spy of ArrayList
    ArrayList<Integer> mockList = Mockito.mock(ArrayList.class);
    ArrayList<Integer> spyList = Mockito.spy(new ArrayList<>());

    // Add elements to both
    mockList.add(1);
    spyList.add(1);

    // Verify interactions (both will pass)
    Mockito.verify(mockList).add(1);
    Mockito.verify(spyList).add(1);

    // Check the size of both lists
    System.out.println("Mock vs Spy - Mock List Size: " + mockList.size());
    System.out.println("Mock vs Spy - Spy List Size: " + spyList.size());

    // Mock returns default values (0 for size) unless stubbed
    assertEquals(0, mockList.size());

    // Spy uses real method implementation, so size is 1
    assertEquals(1, spyList.size());

    // Stub both to return size 100
    Mockito.when(mockList.size()).thenReturn(100);
    Mockito.when(spyList.size()).thenReturn(100);

    // Both should now return 100 for size
    assertEquals(100, mockList.size());
    assertEquals(100, spyList.size());

    System.out.println("Mock vs Spy (After Stubbing) - Mock List Size: " + mockList.size());
    System.out.println("Mock vs Spy (After Stubbing) - Spy List Size: " + spyList.size());
}

Verständnis der wesentlichen Unterschiede

Lassen Sie uns die wesentlichen Unterschiede zwischen Mocks und Spies in Mockito zusammenfassen:

  1. Methodenverhalten:

    • Mock: Alle Methoden geben Standardwerte (null, 0, false) zurück, es sei denn, sie werden stubbed.
    • Spy: Methoden rufen die echte Implementierung auf, es sei denn, sie werden stubbed.
  2. Objektzustand:

    • Mock: Operationen ändern den Zustand nicht (z.B. das Hinzufügen zu einer Mock-Liste fügt tatsächlich nichts hinzu).
    • Spy: Operationen ändern den Zustand des Objekts genauso wie bei einem echten Objekt.
  3. Erstellung:

    • Mock: Wird aus einer Klasse, nicht aus einer Instanz erstellt (Mockito.mock(ArrayList.class)).
    • Spy: Wird normalerweise aus einer Instanz erstellt (Mockito.spy(new ArrayList<>())).
  4. Anwendungsfälle:

    • Mock: Wenn Sie das Verhalten vollständig kontrollieren möchten und keine echten Methoden benötigen.
    • Spy: Wenn Sie echte Methoden verwenden möchten, aber nur einige Verhaltensweisen überschreiben müssen.

Praktisches Beispiel: Wann man einen Spy verwendet

Fügen wir noch einen Test hinzu, der einen häufigen Anwendungsfall für Spies zeigt - das partielle Stubbing eines komplexen Objekts:

@Test
public void testWhenToUseSpy() {
    // Create a complex data object that we want to partially mock
    StringBuilder builder = new StringBuilder();
    StringBuilder spyBuilder = Mockito.spy(builder);

    // Use the real append method
    spyBuilder.append("Hello");
    spyBuilder.append(" World");

    // Verify the real methods were called
    Mockito.verify(spyBuilder).append("Hello");
    Mockito.verify(spyBuilder).append(" World");

    // The real methods modified the state
    assertEquals("Hello World", spyBuilder.toString());
    System.out.println("When to Use Spy - Content: " + spyBuilder.toString());

    // Stub the toString() method to return something else
    Mockito.when(spyBuilder.toString()).thenReturn("Stubbed String");

    // Now toString() returns our stubbed value
    assertEquals("Stubbed String", spyBuilder.toString());
    System.out.println("When to Use Spy - After Stubbing toString(): " + spyBuilder.toString());

    // But other methods still work normally
    spyBuilder.append("!");
    Mockito.verify(spyBuilder).append("!");

    // toString() still returns our stubbed value
    assertEquals("Stubbed String", spyBuilder.toString());
}

Lassen Sie uns unseren aktualisierten Code kompilieren:

cd ~/project
javac -cp lib/junit.jar:lib/mockito-core.jar:lib/byte-buddy.jar:lib/byte-buddy-agent.jar:lib/objenesis.jar:lib/hamcrest-core.jar MockitoSpyDemo.java

Der Code sollte ohne Fehler kompilieren. In diesem Schritt haben wir die wesentlichen Unterschiede zwischen Mocks und Spies gelernt und wann man welche verwendet. Mocks ersetzen vollständig das Verhalten des ursprünglichen Objekts, während Spies das ursprüngliche Verhalten beibehalten, aber es ermöglichen, bestimmte Methoden bei Bedarf zu überschreiben.

Zusammenfassung

In diesem Lab haben wir das Konzept von Spies in Mockito, einer leistungsstarken Funktion für Java-Unit-Tests, untersucht. Hier sind die wichtigsten Punkte, die wir behandelt haben:

  1. Erstellen von Spies: Wir haben gelernt, wie man Spies sowohl mit der Methode Mockito.spy() als auch mit der Annotation @Spy erstellt. Die erste Methode ist flexibler, während die Annotation praktischer ist, wenn man viele Spies hat.

  2. Verhalten von Spies: Spies verfolgen Methodenaufrufe wie Mocks, führen aber standardmäßig die echten Methoden aus. Diese doppelte Natur macht sie besonders nützlich für das Testen von echten Objekten, während man dennoch die Möglichkeit hat, Interaktionen zu überprüfen.

  3. Stubbing von Spies: Wir haben herausgefunden, wie man das Standardverhalten von Spy-Methoden mit doReturn(), doThrow() und anderen Stubbing-Methoden überschreiben kann. Dies ermöglicht es uns, bestimmte Verhaltensweisen zu steuern, während andere intakt bleiben.

  4. Ausnahmebehandlung: Wir haben die NotAMockException untersucht, die auftritt, wenn man versucht, Mockito-Überprüfungsmethoden auf normale Objekte anzuwenden, und wie man sie richtig behandelt.

  5. Mock vs. Spy: Wir haben Mocks und Spies verglichen, um ihre wesentlichen Unterschiede zu verstehen. Mocks ersetzen das ursprüngliche Verhalten vollständig durch Standard- oder stubbed Antworten, während Spies das ursprüngliche Verhalten beibehalten, aber das Überschreiben bestimmter Methoden ermöglichen.

Spies sind ein leistungsstarkes Werkzeug in der Mockito-Arsenal, das ein Gleichgewicht zwischen der vollständigen Kontrolle von Mocks und der Realität echter Objekte bietet. Sie sind besonders nützlich, wenn:

  • Sie ein echtes Objekt testen möchten, aber bestimmte Interaktionen überprüfen müssen
  • Sie nur bestimmte Methoden überschreiben müssen, während das restliche Verhalten des Objekts intakt bleibt
  • Sie mit komplexen Objekten arbeiten, bei denen das Mocken aller Methoden unpraktikabel wäre

Indem Sie verstehen, wann und wie man Spies verwendet, können Sie effektivere und wartbarere Unit-Tests für Ihre Java-Anwendungen schreiben.