617 Stimmen

Mockito verwenden, um einige Methoden nachzubilden, andere jedoch nicht

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

Zum Beispiel in dieser (zugegebenermaßen erfundenen) Stock Klasse möchte ich die getPrice() y getQuantity() Rückgabewerte (wie im folgenden Testfragment gezeigt), aber ich möchte die getValue() um die Multiplikation durchzuführen, wie sie in der Stock Klasse

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);
}

973voto

Jon Newmuis Punkte 24952

Um Ihre Frage direkt zu beantworten: Ja, Sie können einige Methoden verspotten, ohne andere zu verspotten. Dies wird als teilweise Attrappe . Siehe die Mockito-Dokumentation über partielle Mocks für weitere Informationen.

Für Ihr Beispiel können Sie in Ihrem Test etwa 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 nachgebildet, es sei denn, die thenCallRealMethod() im when(..) Klausel.

Es besteht auch die Möglichkeit des umgekehrten Weges mit Spionage anstelle von Nachahmung :

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 die echten, es sei denn, Sie haben ein gespottetes Verhalten mit when(..) .

Es gibt einen wichtigen Fallstrick bei der Verwendung von when(Object) mit spy wie im vorherigen Beispiel. Die eigentliche Methode wird aufgerufen (weil stock.getPrice() wird ausgewertet, bevor when(..) zur Laufzeit). Dies kann ein Problem sein, wenn Ihre Methode Logik enthält, die nicht aufgerufen werden sollte. Sie können das vorherige Beispiel wie folgt 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 wäre die Verwendung von org.mockito.Mockito.CALLS_REAL_METHODS wie zum Beispiel:

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

Dies delegiert unbearbeitete Aufrufe an echte Implementierungen.


Bei Ihrem Beispiel glaube ich jedoch, dass es trotzdem scheitern wird, da die Implementierung von getValue() stützt sich auf quantity y price statt getQuantity() y getPrice() Das ist es, worüber Sie sich lustig gemacht haben.

Eine andere Möglichkeit ist, Mocks ganz 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);
}

176voto

Sudarshan Punkte 8334

Partielles Mocking einer Klasse wird auch unterstützt durch Spionage in mockito

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());

Prüfen Sie die 1.10.19 y 2.7.22 docs für eine detaillierte Erklärung.

47voto

ema Punkte 812

Según docs :

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();

21voto

the dude Punkte 780

Was Sie wollen, ist org.mockito.Mockito.CALLS_REAL_METHODS laut den Unterlagen:

/**
 * 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>
 */

Ihr Code sollte also wie folgt 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);
    }
}

Die Aufforderung an Stock stock = mock(Stock.class); ruft auf. org.mockito.Mockito.mock(Class<T>) die wie folgt aussieht:

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

Die Dokumente des Wertes RETURNS_DEFAULTS erzählen:

/**
 * 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.)
 */

3voto

kflGalore Punkte 59

Partielles Mocking mit der Spy-Methode von Mockito könnte die Lösung für Ihr Problem sein, wie bereits in den Antworten oben erwähnt. Bis zu einem gewissen Grad stimme ich zu, dass es für Ihren konkreten Anwendungsfall angemessener sein könnte, den DB-Lookup zu mocken. Meiner Erfahrung nach ist dies nicht immer möglich - zumindest nicht ohne andere Workarounds - die ich als sehr umständlich oder zumindest fragil ansehen würde. Beachten Sie, dass partielles Mocking nicht mit allen Versionen von Mockito funktioniert. Sie müssen mindestens 1.8.0 verwenden.

Ich hätte einfach einen einfachen Kommentar zur ursprünglichen Frage geschrieben, anstatt diese Antwort zu posten, aber StackOverflow erlaubt dies nicht.

Nur noch eine Sache: Ich kann wirklich nicht verstehen, dass eine Frage, die hier gestellt wird, oft mit "Warum wollen Sie das tun" kommentiert wird, ohne dass man wenigstens versucht, das Problem zu verstehen. Gerade wenn es um die Notwendigkeit von partiellem Mocking geht, gibt es wirklich viele Anwendungsfälle, bei denen ich mir vorstellen könnte, dass es nützlich wäre. Aus diesem Grund haben die Jungs von Mockito diese Funktionalität bereitgestellt. Diese Funktion sollte natürlich nicht überstrapaziert werden. Aber wenn es um die Einrichtung von Testfällen geht, die sonst nicht auf sehr komplizierte Weise erstellt werden könnten, sollte Spying eingesetzt werden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X