Usa Mockito per deridere alcuni metodi ma non altri


402

Esiste un modo, usando Mockito, per deridere alcuni metodi in una classe, ma non altri?

Ad esempio, in questa Stockclasse (dichiaratamente inventata) voglio deridere i valori getPrice()e getQuantity()restituire (come mostrato nello snippet di prova di seguito) ma voglio getValue()eseguire la moltiplicazione come codificato nella Stockclasse

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

4
Perché vorresti farlo? Dovresti o testare la classe (nel qual caso, non ci dovrebbe essere beffardo) o dovresti deriderla durante il test di una classe diversa (nel qual caso, nessuna funzionalità). Perché dovresti fare una derisione parziale?
weltraumpirat,

3
Ok, questo è un piccolo esempio della cosa reale. In realtà, sto cercando di evitare una chiamata al database, passando valori forzati, ma voglio verificare che gli altri metodi funzionino correttamente con quei valori forzati. C'è un modo migliore per farlo?
Victor Grazi,

5
Certamente: sposta le tue chiamate al database in una classe separata (la logica del dominio e l'accesso al database non dovrebbero essere nella stessa classe; sono due preoccupazioni diverse), estrai la sua interfaccia, usa quella interfaccia per connetterti dalla classe della logica del dominio e prendi in giro solo il interfaccia durante i test.
weltraumpirat,

1
Sono completamente d'accordo, è difficile spiegare l'intera immagine senza caricare gobs di codice qui, comprese le librerie di terze parti.
Victor Grazi,

1
Probabilmente potresti. Ma non sarebbe "un modo migliore per farlo": il codice del database è un dettaglio dell'implementazione che si desidera nascondere al resto dell'applicazione, probabilmente anche passare a un pacchetto diverso. Non dovresti dover ricompilare la logica del tuo dominio ogni volta che cambi un'istruzione sequel, vero?
weltraumpirat,

Risposte:


644

Per rispondere direttamente alla tua domanda, sì, puoi deridere alcuni metodi senza deridere altri. Questo si chiama derisione parziale . Vedere la documentazione di Mockito su simulazioni parziali per ulteriori informazioni.

Per il tuo esempio, puoi fare qualcosa come il seguente, nel tuo test:

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 tal caso, ogni implementazione del metodo viene derisa, a meno che non sia specificato thenCallRealMethod()nella when(..)clausola.

C'è anche una possibilità viceversa con spia invece di derisione :

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 tal caso, tutte le implementazioni del metodo sono quelle reali, tranne se hai definito un comportamento beffardo con when(..).

C'è una trappola importante quando si usa la when(Object)spia come nell'esempio precedente. Verrà chiamato il metodo reale (perché stock.getPrice()viene valutato prima when(..)in fase di esecuzione). Questo può essere un problema se il tuo metodo contiene una logica che non dovrebbe essere chiamata. Puoi scrivere l'esempio precedente in questo modo:

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

Un'altra possibilità potrebbe essere quella di utilizzare org.mockito.Mockito.CALLS_REAL_METHODS, come ad esempio:

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

Ciò delega le chiamate non controllate a implementazioni reali.


Tuttavia, con il tuo esempio, credo che fallirà ancora, poiché l'implementazione di getValue()fa affidamento quantitye price, piuttosto che getQuantity()e getPrice(), che è ciò che hai deriso.

Un'altra possibilità è quella di evitare del tutto le derisioni:

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

21
Penso che questa risposta sia sbagliata. È necessario SPY un'istanza dell'oggetto, non MOCK la classe.
GaRRaPeTa,

2
@GaRRaPeTa Direi che spionaggio e derisione sono entrambe alternative ragionevoli. È difficile dire quale sia la migliore per questo caso, poiché l'OP afferma che questo è un esempio semplificato.
Jon Newmuis,

1
Non dovrebbe essere "Spy" invece di "Mock" in quanto la cabina di derisione parziale potrebbe essere fornita da "Spy" in un modo migliore.
Tarun Sapra,

2
Stock stock = spy(Stock.class);Questo sembra sbagliato, il spymetodo sembra accettare solo oggetti e non classi.
Paramvir Singh Karwal,

4
+1 per indicare la differenza tra doReturn(retval).when(spyObj).methodName(args)ewhen(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious

140

Il derisione parziale di una classe è supportato anche tramite Spy 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());

Controlla i documenti 1.10.19e 2.7.22per una spiegazione dettagliata.


37

Secondo i documenti :

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

2
Grazie per aver dimostrato come impostare una simulazione in cui viene chiamata la vera implementazione per tutti i metodi tranne i pochi che devo controllare dal test.
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()); }Questo non funziona Qualunque sia la ragione, quando "quando" viene eseguito, esegue effettivamente il metodo che dovrebbe essere deriso. Codice:
Lance Kind,

3
Il problema è "quando". Il "quando" eseguirà effettivamente la cosa che vuoi deridere parzialmente. Per evitare ciò esiste un'alternativa: doReturn (). Vedi doReturn () su docs.mockito.googlecode.com/hg/1.9.5/org/mockito/…
Lance Kind,

18

Quello che vuoi è org.mockito.Mockito.CALLS_REAL_METHODSsecondo i documenti:

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

Quindi il tuo codice dovrebbe apparire come:

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

La chiamata alle Stock stock = mock(Stock.class);chiamate org.mockito.Mockito.mock(Class<T>)che assomiglia a questa:

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

I documenti del valore RETURNS_DEFAULTSdicono:

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

1
Ben individuato ... ma posso solo chiederti perché usi withSettings()...così? Sembra che org.mockito.internal.stubbing.answers.CallsRealMethods()(per esempio) potrebbe fare il lavoro ... e il javadoc per questa classe dice specificamente che è per l'uso per derisioni parziali ...
mike rodent,

3
Inoltre ... questo non si imbatterà nel problema riscontrato da altre risposte qui: cioè thenReturneseguirà effettivamente il metodo (che potrebbe causare problemi, anche se non in questo esempio), e quindi doReturnè preferibile in questo caso ...?
mike rodent,

4

Deridere parzialmente usando il metodo spia di Mockito potrebbe essere la soluzione al tuo problema, come già affermato nelle risposte sopra. In una certa misura concordo sul fatto che, per il tuo caso d'uso concreto, potrebbe essere più appropriato deridere la ricerca del DB. Dalla mia esperienza questo non è sempre possibile - almeno non senza altre soluzioni alternative - che considererei molto ingombrante o almeno fragile. Nota che il derisione parziale non funziona con le versioni alleate di Mockito. Hai usato almeno 1.8.0.

Avrei appena scritto un semplice commento per la domanda originale invece di pubblicare questa risposta, ma StackOverflow non lo consente.

Solo un'altra cosa: non riesco davvero a capire che molte volte una domanda viene posta qui ottiene un commento con "Perché vuoi farlo" senza almeno cercare di capire il problema. Soprattutto quando si tratta quindi di deridere parzialmente ci sono davvero molti casi d'uso che potrei immaginare dove sarebbe utile. Ecco perché i ragazzi di Mockito hanno fornito questa funzionalità. Naturalmente questa funzione non dovrebbe essere abusata. Ma quando parliamo di configurazioni di casi di test che altrimenti non potrebbero essere stabilite in modo molto complicato, si dovrebbe usare lo spionaggio.


2
Sento che questa risposta è in parte un'opinione. Si prega di considerare la modifica.
soundlikeodd

2
Ha votato per rallegrare il nuovo membro della famiglia. Non c'è bisogno di entrare in questa zona, niente di tecnicamente sbagliato lì o linguaggio / tono errati. Sii gentile con i nuovi membri. Grazie.
Saurabh Patil,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.