web-dev-qa-db-fra.com

Utilisez Mockito pour se moquer de certaines méthodes mais pas d'autres

Y at-il un moyen, en utilisant Mockito, de se moquer de certaines méthodes dans une classe, mais pas d’autres?

Par exemple, dans cette classe d'actions (certes conçue), je veux me moquer des valeurs renvoyées par getPrice () et getQuantity () (comme indiqué dans l'extrait de test ci-dessous), mais je veux que getValue () effectue la multiplication telle que codée dans l'action classe

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);
}
289
Victor Grazi

Pour répondre directement à votre question, vous pouvez vous moquer de certaines méthodes sans vous moquer des autres. Cela s'appelle un partial mock. Voir la documentation de Mockito sur les modèles partiels pour plus d'informations.

Pour votre exemple, vous pouvez faire quelque chose comme ceci, dans votre 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

Dans ce cas, chaque implémentation de méthode est simulée, sauf si vous spécifiez thenCallRealMethod() dans la clause when(..).

Il y a aussi une possibilité dans l'autre sens avec spy au lieu de mock:

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

Dans ce cas, toutes les implémentations de méthodes sont réelles, sauf si vous avez défini un comportement fictif avec when(..).

Il y a un écueil important lorsque vous utilisez when(Object) avec spy comme dans l'exemple précédent. La vraie méthode sera appelée (car stock.getPrice() est évalué avant when(..) au moment de l'exécution). Cela peut poser problème si votre méthode contient une logique qui ne devrait pas être appelée. Vous pouvez écrire l'exemple précédent comme ceci:

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

Cependant, avec votre exemple, je pense que cela échouera toujours, car la mise en œuvre de getValue() repose sur quantity et price, plutôt que getQuantity() et getPrice(), ce dont vous vous êtes moqués.

Ce que vous voulez vraiment, c'est simplement:

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

Les moquages ​​partiels d'une classe sont également supportés via Spy dans 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());

Vérifiez les 1.10.19 et 2.7.22 docs pour une explication détaillée.

119
Sudarshan

Selon 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();
21
ema

La réponse acceptée n'est pas correcte selon la question.

L'appel à Stock stock = mock(Stock.class); appelle org.mockito.Mockito.mock(Class<T>) qui ressemble à ceci:

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

Les documents de la valeur RETURNS_DEFAULTS disent:

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

Ce que vous voulez, c'est org.mockito.Mockito.CALLS_REAL_METHODS en fonction de la documentation:

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

Ainsi, votre code devrait ressembler à:

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);
    }
}
15
the dude

Comme vous l'avez déjà indiqué dans les réponses ci-dessus, une solution partielle à l'aide de la méthode d'espionnage de Mockito pourrait être une moquerie partielle. Dans une certaine mesure, je conviens que, pour votre cas d'utilisation concret, il peut être plus approprié de se moquer de la recherche de base de données. De par mon expérience, cela n’est pas toujours possible - du moins pas sans d’autres solutions - que j’aimerais considérer comme très lourd ou du moins fragile. Notez que ce moquage partiel ne fonctionne pas avec les versions alliées de Mockito. Vous avez utilisé au moins 1.8.0.

J'aurais juste écrit un simple commentaire pour la question initiale au lieu de poster cette réponse, mais StackOverflow ne le permet pas.

Encore une chose: je ne peux vraiment pas comprendre le fait que bien souvent une question est posée ici et que le commentaire "Pourquoi tu veux faire ça" sans au moins essayer de comprendre le problème. En ce qui concerne le besoin de moquerie partielle, il y a vraiment beaucoup de cas d'utilisation que je pourrais imaginer où ce serait utile. C'est pourquoi les gars de Mockito ont fourni cette fonctionnalité. Cette fonctionnalité ne doit bien sûr pas être surutilisée. Mais lorsque nous parlons de configurations de cas de test qui, autrement, ne pourraient pas être établies de manière très compliquée, l’espionnage devrait être utilisé. 

0
kflGalore