Utilice Mockito para burlarse de algunos métodos pero no de otros

Resuelto Victor Grazi asked hace 11 años • 5 respuestas

¿Hay alguna manera, usando Mockito, de burlarse de algunos métodos en una clase, pero no de otros?

Por ejemplo, en esta Stockclase (ciertamente artificial) quiero burlarme de los valores getPrice()y getQuantity()devolver (como se muestra en el fragmento de prueba a continuación) pero quiero getValue()realizar la multiplicación como está codificada en la Stockclase .

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 avatar Feb 20 '13 07:02 Victor Grazi
Aceptado

Para responder directamente a tu pregunta, sí, puedes burlarte de algunos métodos sin burlarte de otros. Esto se llama simulacro parcial . Consulte la documentación de Mockito sobre simulaciones parciales para obtener más información.

Para su ejemplo, puede hacer algo como lo siguiente en su prueba:

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

En ese caso, se burla la implementación de cada método, a menos que se especifique thenCallRealMethod()en la when(..)cláusula.

También existe la posibilidad al revés con spy en lugar de simulacro :

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

En ese caso, todas las implementaciones de métodos son reales, excepto si ha definido un comportamiento simulado con when(..).

Hay un problema importante cuando se utiliza when(Object)con espía como en el ejemplo anterior. Se llamará al método real (porque stock.getPrice()se evalúa antes when(..)en tiempo de ejecución). Esto puede ser un problema si su método contiene lógica que no debería llamarse. Puedes escribir el ejemplo anterior así:

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

Otra posibilidad puede ser utilizar org.mockito.Mockito.CALLS_REAL_METHODS, como por ejemplo:

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

Esto delega llamadas no bloqueadas a implementaciones reales.


Sin embargo, con su ejemplo, creo que seguirá fallando, ya que la implementación de getValue()depende de quantityand price, en lugar de getQuantity()and getPrice(), que es de lo que se ha burlado.

Otra posibilidad es evitar las burlas por completo:

@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 avatar Feb 20 '2013 01:02 Jon Newmuis

La burla parcial de una clase también se admite a través de Spy en 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());

Consulte los documentos 1.10.19y 2.7.22para obtener una explicación detallada.

Sudarshan avatar May 24 '2013 06:05 Sudarshan

Según los documentos :

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 avatar Sep 01 '2015 13:09 ema

Lo que quieres es org.mockito.Mockito.CALLS_REAL_METHODSsegún los documentos:

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

Por lo tanto, su código debería verse así:

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 llamada a Stock stock = mock(Stock.class);llamadas org.mockito.Mockito.mock(Class<T>)que se ve así:

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

Los documentos del valor RETURNS_DEFAULTSdicen:

/**
 * 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.)
 */
David Baldin avatar Feb 19 '2016 11:02 David Baldin