Verwenden Sie Mockito, um einige Methoden zu verspotten, andere jedoch nicht

402

Gibt es eine Möglichkeit, mit Mockito einige Methoden in einer Klasse zu verspotten, andere jedoch nicht?

Zum Beispiel Stockmöchte ich in dieser (zugegebenermaßen erfundenen) Klasse die Werte verspotten getPrice()und getQuantity()zurückgeben (wie im Test-Snippet unten gezeigt), aber ich möchte getValue(), dass die Multiplikation wie in der StockKlasse codiert durchgeführt wird

public class Stock {
  private final double price;
  private final int quantity;

  Stock(double price, int quantity) {
    this.price = price;
    this.quantity = quantity;
  }

  public double getPrice() {
    return price;
  }

  public int getQuantity() {
    return quantity;
  }
  public double getValue() {
    return getPrice() * getQuantity();
  }

  @Test
  public void getValueTest() {
    Stock stock = mock(Stock.class);
    when(stock.getPrice()).thenReturn(100.00);
    when(stock.getQuantity()).thenReturn(200);
    double value = stock.getValue();
    // Unfortunately the following assert fails, because the mock Stock getValue() method does not perform the Stock.getValue() calculation code.
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}
Victor Grazi
quelle
4
Warum willst du das tun? Sie sollten entweder die Klasse testen (in diesem Fall sollte es überhaupt keine Verspottung geben) oder Sie sollten sie verspotten, während Sie eine andere Klasse testen (in diesem Fall keine Funktionalität). Warum würdest du einen Teil verspotten?
Weltraumpirat
3
Ok, das ist ein kleines Beispiel für die Realität. In Wirklichkeit versuche ich, einen Aufruf der Datenbank zu vermeiden, indem ich erfundene Werte übergebe, aber ich möchte überprüfen, ob die anderen Methoden mit diesen erfundenen Werten korrekt funktionieren. Gibt es einen besseren Weg, dies zu tun?
Victor Grazi
5
Sicher: Verschieben Sie Ihre Datenbankaufrufe in eine separate Klasse (Domänenlogik und Datenbankzugriff sollten nicht in derselben Klasse sein; es handelt sich um zwei verschiedene Probleme), extrahieren Sie die Schnittstelle, verwenden Sie diese Schnittstelle, um eine Verbindung von der Domänenlogikklasse herzustellen, und verspotten Sie nur die Schnittstelle während des Testens.
Weltraumpirat
1
Ich stimme vollkommen zu, es ist schwierig, das ganze Bild zu erklären, ohne hier Code-Gobs hochzuladen, einschließlich Bibliotheken von Drittanbietern.
Victor Grazi
1
Sie könnten wahrscheinlich. Aber dann wäre das kein "besserer Weg": Ihr Datenbankcode ist ein Implementierungsdetail, das Sie vor dem Rest Ihrer Anwendung verbergen möchten, wahrscheinlich sogar in ein anderes Paket verschieben möchten. Sie möchten Ihre Domain-Logik nicht jedes Mal neu kompilieren müssen, wenn Sie eine Folge-Anweisung ändern, oder?
Weltraumpirat

Antworten:

643

Um Ihre Frage direkt zu beantworten: Ja, Sie können einige Methoden verspotten, ohne andere zu verspotten. Dies wird als partieller Schein bezeichnet . Weitere Informationen finden Sie in der Mockito-Dokumentation zu Teilverspottungen .

Für Ihr Beispiel können Sie in Ihrem Test Folgendes tun:

Stock stock = mock(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
when(stock.getValue()).thenCallRealMethod();  // Real implementation

In diesem Fall wird jede Methodenimplementierung verspottet, sofern thenCallRealMethod()in der when(..)Klausel nichts anderes angegeben ist.

Es gibt auch die Möglichkeit umgekehrt mit Spion statt Spott :

Stock stock = spy(Stock.class);
when(stock.getPrice()).thenReturn(100.00);    // Mock implementation
when(stock.getQuantity()).thenReturn(200);    // Mock implementation
// All other method call will use the real implementations

In diesem Fall sind alle Methodenimplementierungen real, es sei denn, Sie haben ein verspottetes Verhalten mit definiert when(..).

Es gibt eine wichtige Gefahr, wenn Sie when(Object)mit Spion wie im vorherigen Beispiel verwenden. Die reale Methode wird aufgerufen (da sie stock.getPrice()zuvor when(..)zur Laufzeit ausgewertet wird). Dies kann ein Problem sein, wenn Ihre Methode Logik enthält, die nicht aufgerufen werden sollte. Sie können das vorherige Beispiel folgendermaßen schreiben:

Stock stock = spy(Stock.class);
doReturn(100.00).when(stock).getPrice();    // Mock implementation
doReturn(200).when(stock).getQuantity();    // Mock implementation
// All other method call will use the real implementations

Eine andere Möglichkeit kann sein, zu verwenden org.mockito.Mockito.CALLS_REAL_METHODS, wie zum Beispiel:

Stock MOCK_STOCK = Mockito.mock( Stock.class, CALLS_REAL_METHODS );

Dies delegiert nicht gestubbte Aufrufe an reale Implementierungen.


Doch mit Ihrem Beispiel, ich glaube , es wird immer noch fehlschlagen, da die Umsetzung getValue()beruht auf quantityund pricestatt getQuantity()und getPrice(), das ist , was Sie verspottet haben.

Eine andere Möglichkeit besteht darin, Verspottungen insgesamt zu vermeiden:

@Test
public void getValueTest() {
    Stock stock = new Stock(100.00, 200);
    double value = stock.getValue();
    assertEquals("Stock value not correct", 100.00*200, value, .00001);
}
Jon Newmuis
quelle
21
Ich denke, diese Antwort ist falsch. Sie müssen eine Instanz des Objekts SPYEN, nicht die Klasse verspotten.
GaRRaPeTa
2
@GaRRaPeTa Ich würde sagen, Spionage und Spott sind beide vernünftige Alternativen. Es ist schwer zu sagen, welches für diesen Fall das Beste ist, da das OP angibt, dass dies ein vereinfachtes Beispiel ist.
Jon Newmuis
1
Sollte es nicht "Spy" anstelle von "Mock" sein, da "Spy" das teilweise verspottende Baby besser zur Verfügung stellen kann?
Tarun Sapra
2
Stock stock = spy(Stock.class);Dies scheint falsch zu sein, die spyMethode scheint nur Objekte zu akzeptieren, keine Klassen.
Paramvir Singh Karwal
4
+1 für den Hinweis auf den Unterschied zwischen doReturn(retval).when(spyObj).methodName(args)undwhen(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious
140

Das teilweise Verspotten einer Klasse wird auch über Spy in mockito unterstützt

List list = new LinkedList();
List spy = spy(list);

//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);

//using the spy calls real methods
spy.add("one");
spy.add("two");

//size() method was stubbed - 100 is printed
System.out.println(spy.size());

Überprüfen Sie die 1.10.19und 2.7.22Dokumente für eine detaillierte Erklärung.

Sudarshan
quelle
37

Laut Dokumentation :

Foo mock = mock(Foo.class, CALLS_REAL_METHODS);

// this calls the real implementation of Foo.getSomething()
value = mock.getSomething();

when(mock.getSomething()).thenReturn(fakeValue);

// now fakeValue is returned
value = mock.getSomething();
ema
quelle
2
Vielen Dank, dass Sie demonstriert haben, wie Sie ein Modell einrichten, bei dem die eigentliche Implementierung für alle Methoden außer den wenigen, die ich vom Test aus steuern muss, aufgerufen wird.
bigh_29
class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); }Das funktioniert nicht. Aus welchem ​​Grund auch immer, wenn "wann" ausgeführt wird, führt es tatsächlich die Methode aus, die verspottet werden soll. Code:
Lance Kind
3
Das Problem ist "wann". Das "Wann" führt tatsächlich das aus, was Sie teilweise verspotten möchten. Um dies zu vermeiden, gibt es eine Alternative: doReturn (). Siehe doReturn () unter docs.mockito.googlecode.com/hg/1.9.5/org/mockito/…
Lance Kind
18

Was Sie wollen, ist org.mockito.Mockito.CALLS_REAL_METHODSgemäß den Dokumenten:

/**
 * Optional <code>Answer</code> to be used with {@link Mockito#mock(Class, Answer)}
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations.
 * <p>
 * This implementation can be helpful when working with legacy code.
 * When this implementation is used, unstubbed methods will delegate to the real implementation.
 * This is a way to create a partial mock object that calls real methods by default.
 * <p>
 * As usual you are going to read <b>the partial mock warning</b>:
 * Object oriented programming is more less tackling complexity by dividing the complexity into separate, specific, SRPy objects.
 * How does partial mock fit into this paradigm? Well, it just doesn't... 
 * Partial mock usually means that the complexity has been moved to a different method on the same object.
 * In most cases, this is not the way you want to design your application.
 * <p>
 * However, there are rare cases when partial mocks come handy: 
 * dealing with code you cannot change easily (3rd party interfaces, interim refactoring of legacy code etc.)
 * However, I wouldn't use partial mocks for new, test-driven & well-designed code.
 * <p>
 * Example:
 * <pre class="code"><code class="java">
 * Foo mock = mock(Foo.class, CALLS_REAL_METHODS);
 *
 * // this calls the real implementation of Foo.getSomething()
 * value = mock.getSomething();
 *
 * when(mock.getSomething()).thenReturn(fakeValue);
 *
 * // now fakeValue is returned
 * value = mock.getSomething();
 * </code></pre>
 */

So sollte Ihr Code aussehen:

import org.junit.Test;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;

public class StockTest {

    public class Stock {
        private final double price;
        private final int quantity;

        Stock(double price, int quantity) {
            this.price = price;
            this.quantity = quantity;
        }

        public double getPrice() {
            return price;
        }

        public int getQuantity() {
            return quantity;
        }

        public double getValue() {
            return getPrice() * getQuantity();
        }
    }

    @Test
    public void getValueTest() {
        Stock stock = mock(Stock.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));
        when(stock.getPrice()).thenReturn(100.00);
        when(stock.getQuantity()).thenReturn(200);
        double value = stock.getValue();

        assertEquals("Stock value not correct", 100.00 * 200, value, .00001);
    }
}

Der Anruf zu Stock stock = mock(Stock.class);Anrufen, org.mockito.Mockito.mock(Class<T>)der so aussieht:

 public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings().defaultAnswer(RETURNS_DEFAULTS));
}

Die Dokumente des Werts RETURNS_DEFAULTSsagen:

/**
 * The default <code>Answer</code> of every mock <b>if</b> the mock was not stubbed.
 * Typically it just returns some empty value. 
 * <p>
 * {@link Answer} can be used to define the return values of unstubbed invocations. 
 * <p>
 * This implementation first tries the global configuration. 
 * If there is no global configuration then it uses {@link ReturnsEmptyValues} (returns zeros, empty collections, nulls, etc.)
 */
der Typ
quelle
1
Gut gesehen ... aber kann ich nur fragen, warum Sie so verwenden withSettings()...? Es scheint, dass org.mockito.internal.stubbing.answers.CallsRealMethods()(zum Beispiel) die Arbeit machen könnte ... und der Javadoc für diese Klasse sagt ausdrücklich, dass es für Teilspott verwendet wird ...
Mike Nagetier
3
Auch ... wird dies nicht auf das Problem stoßen, auf das andere Antworten hier stoßen: dh thenReturnwird die Methode tatsächlich ausgeführt (was Probleme verursachen kann, obwohl dies in diesem Beispiel nicht der doReturnFall ist ), und ist daher in einem solchen Fall vorzuziehen ...?
Mike Nagetier
4

Teilweise Verspottung mit Mockitos Spionagemethode könnte die Lösung für Ihr Problem sein, wie bereits in den obigen Antworten angegeben. Bis zu einem gewissen Grad stimme ich zu, dass es für Ihren konkreten Anwendungsfall möglicherweise angemessener ist, die DB-Suche zu verspotten. Aus meiner Erfahrung ist dies nicht immer möglich - zumindest nicht ohne andere Problemumgehungen -, die ich als sehr umständlich oder zumindest fragil betrachten würde. Beachten Sie, dass partielles Verspotten nicht mit Verbündetenversionen von Mockito funktioniert. Sie haben mindestens 1.8.0 verwendet.

Ich hätte nur einen einfachen Kommentar für die ursprüngliche Frage geschrieben, anstatt diese Antwort zu veröffentlichen, aber StackOverflow erlaubt dies nicht.

Nur noch eine Sache: Ich kann wirklich nicht verstehen, dass hier oft eine Frage gestellt wird, die einen Kommentar mit "Warum Sie das tun wollen" erhält, ohne zumindest zu versuchen, das Problem zu verstehen. Besonders wenn es darum geht, sich teilweise zu verspotten, gibt es wirklich viele Anwendungsfälle, die ich mir vorstellen kann, wo es nützlich wäre. Deshalb haben die Jungs von Mockito diese Funktionalität bereitgestellt. Diese Funktion sollte natürlich nicht überbeansprucht werden. Wenn wir jedoch über Testfall-Setups sprechen, die sonst nicht auf sehr komplizierte Weise erstellt werden könnten, sollte Spionage verwendet werden.

kflGalore
quelle
2
Ich halte diese Antwort teilweise für eine Meinung. Bitte beachten Sie die Bearbeitung.
Soundslikeodd
2
Upvoted, um das neue Mitglied in der Familie aufzuheitern. Keine Notwendigkeit, diese In-V-Zone zu bekommen, nichts wirklich technisch Falsches oder falsche Sprache / Ton. Seien Sie freundlich zu neuen Mitgliedern. Danke.
Saurabh Patil