Użyj Mockito, aby wyśmiewać niektóre metody, ale nie inne

402

Czy jest jakiś sposób, za pomocą Mockito, wyśmiewać niektóre metody w klasie, ale nie inne?

Na przykład w tej (wprawdzie wymyślonej) Stockklasie chcę wyśmiewać wartości getPrice()i getQuantity()zwracać wartości (jak pokazano w fragmencie testowym poniżej), ale chcę, getValue()aby wykonać mnożenie zgodnie z zakodowaniem w Stockklasie

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
źródło
4
Dlaczego chcesz to zrobić? Powinieneś albo testować klasę (w takim przypadku nie powinno być w ogóle szyderstwa), albo powinieneś wyśmiewać ją podczas testowania innej klasy (w takim przypadku brak funkcjonalności). Dlaczego miałbyś robić częściową próbę?
weltraumpirat
3
Ok, to jest mały przykład prawdziwej rzeczy. W rzeczywistości próbuję uniknąć wywołania bazy danych, przekazując wymyślone wartości, ale chcę sprawdzić, czy inne metody działają poprawnie z tymi wymyślonymi wartościami. Czy jest na to lepszy sposób?
Victor Grazi
5
Oczywiście: Przenieś wywołania bazy danych do oddzielnej klasy (logika domeny i dostęp do bazy danych nie powinny należeć do tej samej klasy; są to dwie różne kwestie), wyodrębnij jego interfejs, użyj tego interfejsu do połączenia z klasy logiki domeny i wyśmiewaj tylko interfejs podczas testowania.
weltraumpirat
1
Całkowicie się zgadzam, ciężko jest wyjaśnić cały obraz bez przesyłania tutaj kawałków kodu, w tym bibliotek stron trzecich.
Victor Grazi
1
Prawdopodobnie mógłbyś. Ale to nie byłby „lepszy sposób na zrobienie tego”: kod bazy danych jest szczegółem implementacji, który chcesz ukryć przed resztą aplikacji, prawdopodobnie nawet przenieść do innego pakietu. Czy nie chcesz ponownie kompilować logiki domeny za każdym razem, gdy zmieniasz instrukcję sequel, prawda?
weltraumpirat

Odpowiedzi:

642

Aby bezpośrednio odpowiedzieć na twoje pytanie, tak, możesz wyśmiewać niektóre metody bez wyśmiewania innych. Nazywa się to częściową próbą . Aby uzyskać więcej informacji, zobacz dokumentację Mockito na temat częściowych prób .

Na przykład w teście możesz wykonać następujące czynności:

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

W takim przypadku każda implementacja metody jest wyśmiewana, chyba że określono thenCallRealMethod()w when(..)klauzuli.

Istnieje również możliwość odwrotnej sytuacji ze szpiegiem zamiast kpiną :

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

W takim przypadku cała implementacja metody jest prawdziwa, z wyjątkiem sytuacji, w której zdefiniowano kpiące zachowanie when(..).

Jest jedna ważna pułapka, gdy używasz when(Object)szpiega, jak w poprzednim przykładzie. Wywołana zostanie prawdziwa metoda (ponieważ stock.getPrice()jest sprawdzana przed when(..)uruchomieniem). Może to stanowić problem, jeśli metoda zawiera logikę, której nie należy wywoływać. Możesz napisać poprzedni przykład w ten sposób:

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

Inną możliwością może być użycie org.mockito.Mockito.CALLS_REAL_METHODS, na przykład:

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

To deleguje nieodebrane połączenia do rzeczywistych implementacji.


Jednak ze swoim przykładzie, wierzę, że jeszcze nie, ponieważ realizacja getValue()opiera się na quantityi price, zamiast getQuantity()i getPrice(), co jest, co pan wyśmiewany.

Inną możliwością jest całkowite unikanie kpin:

@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
źródło
21
Myślę, że ta odpowiedź jest zła. Musisz SZPIEĆ instancję obiektu, a NIE MOCK klasy.
GaRRaPeTa
2
@GaRRaPeTa Powiedziałbym, że zarówno szpiegowanie, jak i kpiny są rozsądnymi alternatywami. Trudno powiedzieć, który jest najlepszy w tym przypadku, ponieważ PO stwierdza, że ​​jest to uproszczony przykład.
Jon Newmuis,
1
Czy nie powinien to być „Szpieg” zamiast „Drwina”, ponieważ „Szpieg” lepiej udowadnia częściową szyderczą taksówkę.
Tarun Sapra,
2
Stock stock = spy(Stock.class);Wydaje się to błędne, spymetoda akceptuje tylko obiekty, a nie klasy.
Paramvir Singh Karwal
4
+1 za wskazanie różnicy między doReturn(retval).when(spyObj).methodName(args)iwhen(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious
140

Częściowe drwiny z klasy są również obsługiwane przez Szpiega w 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());

Sprawdź 1.10.19i 2.7.22dokumenty, aby uzyskać szczegółowe wyjaśnienia.

Sudarshan
źródło
37

Według dokumentów :

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
źródło
2
Dziękujemy za pokazanie, jak skonfigurować próbę, w której wywoływana jest prawdziwa implementacja dla wszystkich metod z wyjątkiem kilku, które muszę kontrolować z poziomu testu.
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()); }To nie działa Bez względu na przyczynę, kiedy „kiedy” jest wykonywane, faktycznie wykonuje metodę, która powinna być wyśmiewana. Kod:
Lance Kind
3
Problemem jest „kiedy”. „Kiedy” faktycznie wykona rzecz, którą chcesz częściowo wyszydzić. Aby tego uniknąć, istnieje alternatywa: doReturn (). Zobacz doReturn () na docs.mockito.googlecode.com/hg/1.9.5/org/mockito/…
Lance Kind
18

Co chcesz, to org.mockito.Mockito.CALLS_REAL_METHODSzgodnie z dokumentami:

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

Zatem twój kod powinien wyglądać następująco:

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

Połączenie z Stock stock = mock(Stock.class);połączeniami, org.mockito.Mockito.mock(Class<T>)które wygląda następująco:

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

Dokumenty wartości RETURNS_DEFAULTSmówią:

/**
 * 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.)
 */
koleś
źródło
1
Dobrze zauważony ... ale czy mogę po prostu zapytać, dlaczego tak używasz withSettings()...? Wygląda na to, że org.mockito.internal.stubbing.answers.CallsRealMethods()(na przykład) może wykonać zadanie ... a javadoc dla tej klasy wyraźnie mówi, że jest on przeznaczony do częściowego udawania ...
Mike Rodent
3
Również ... czy to nie napotka problemu napotkanego przez inne odpowiedzi tutaj: tj. thenReturnFaktycznie wykona metodę (która może powodować problemy, chociaż nie w tym przykładzie), a więc doReturnjest lepiej w takim przypadku ...?
Mike Rodent
4

Częściowe drwiny przy użyciu metody szpiegowskiej Mockito mogą być rozwiązaniem twojego problemu, jak już wspomniano w odpowiedziach powyżej. W pewnym stopniu zgadzam się, że w twoim konkretnym przypadku użycia bardziej odpowiednie może być wyśmiewanie wyszukiwania DB. Z mojego doświadczenia nie zawsze jest to możliwe - przynajmniej nie bez innych obejść - które uważałbym za bardzo kłopotliwe lub co najmniej kruche. Pamiętaj, że częściowe kpiny nie działają z sojuszniczymi wersjami Mockito. Używasz co najmniej 1.8.0.

Po prostu napisałbym prosty komentarz do oryginalnego pytania zamiast zamieszczać tę odpowiedź, ale StackOverflow nie pozwala na to.

Jeszcze jedna rzecz: Naprawdę nie rozumiem, że wiele razy tutaj zadawane jest pytanie z komentarzem „Dlaczego chcesz to zrobić” bez przynajmniej próby zrozumienia problemu. Tym bardziej, jeśli chodzi o potrzebę częściowego kpienia, istnieje naprawdę wiele przypadków użycia, które mogłem sobie wyobrazić, gdzie byłoby to przydatne. Właśnie dlatego chłopaki z Mockito zapewnili tę funkcjonalność. Nie należy oczywiście nadużywać tej funkcji. Ale kiedy mówimy o konfiguracjach przypadków testowych, których inaczej nie można byłoby ustalić w bardzo skomplikowany sposób, należy zastosować szpiegostwo.

kflGalore
źródło
2
Czuję, że ta odpowiedź jest częściowo opinią. Proszę rozważyć edycję.
brzmi jakodd
2
Wybitny, aby pocieszyć nowego członka rodziny. Nie ma potrzeby uzyskiwania tej strefy inve, nic tam naprawdę nie jest technicznie złe lub niewłaściwy język / ton. Bądź miły dla nowych członków. Dzięki.
Saurabh Patil,