是否有任何方法,使用Mockito,模拟类中的一些方法,而不是其他方法?

例如,在这个(显然是虚构的)Stock类中,我想模拟getPrice()和getQuantity()返回值(如下面的测试片段所示),但我想让getValue()执行在Stock类中编码的乘法运算

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

当前回答

为了直接回答您的问题,是的,您可以模拟一些方法而不模拟其他方法。这称为部分模拟。有关更多信息,请参阅Mockito关于部分模拟的文档。

对于你的例子,你可以在你的测试中做如下的事情:

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

在这种情况下,每个方法实现都被模拟,除非在when(..)子句中指定了encallrealmethod()。

还有一种可能是spy而不是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

在这种情况下,所有的方法实现都是真正的方法,除非你用when(..)定义了一个模拟行为。

当你像前面的例子一样使用when(Object)和spy时,有一个重要的陷阱。真正的方法将被调用(因为stock.getPrice()在运行时(..)之前被求值)。如果您的方法包含不应该调用的逻辑,这可能会成为一个问题。你可以这样写上面的例子:

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

另一种可能是使用org.mockito.Mockito。CALLS_REAL_METHODS,例如:

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

这将把未存根的调用委托给真正的实现。


但是,对于您的示例,我相信它仍然会失败,因为getValue()的实现依赖于数量和价格,而不是您模拟的getQuantity()和getPrice()。

另一种可能是完全避免mock:

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

其他回答

为了直接回答您的问题,是的,您可以模拟一些方法而不模拟其他方法。这称为部分模拟。有关更多信息,请参阅Mockito关于部分模拟的文档。

对于你的例子,你可以在你的测试中做如下的事情:

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

在这种情况下,每个方法实现都被模拟,除非在when(..)子句中指定了encallrealmethod()。

还有一种可能是spy而不是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

在这种情况下,所有的方法实现都是真正的方法,除非你用when(..)定义了一个模拟行为。

当你像前面的例子一样使用when(Object)和spy时,有一个重要的陷阱。真正的方法将被调用(因为stock.getPrice()在运行时(..)之前被求值)。如果您的方法包含不应该调用的逻辑,这可能会成为一个问题。你可以这样写上面的例子:

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

另一种可能是使用org.mockito.Mockito。CALLS_REAL_METHODS,例如:

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

这将把未存根的调用委托给真正的实现。


但是,对于您的示例,我相信它仍然会失败,因为getValue()的实现依赖于数量和价格,而不是您模拟的getQuantity()和getPrice()。

另一种可能是完全避免mock:

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

你想要的是org。mockito。mockito。根据文档CALLS_REAL_METHODS:

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

因此你的代码应该是这样的:

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

调用Stock Stock = mock(Stock.class);调用org.mockito.Mockito.mock(类<T>),看起来像这样:

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

RETURNS_DEFAULTS值的文档告诉我们:

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

使用Mockito的间谍方法进行部分模拟可以解决您的问题,正如上面的答案所述。在某种程度上,我同意,对于具体的用例,模拟数据库查找可能更合适。从我的经验来看,这并不总是可能的——至少不是没有其他变通办法——我认为这是非常麻烦的,或者至少是脆弱的。请注意,部分模拟不适用于Mockito的盟友版本。您至少使用了1.8.0。

我只是写了一个简单的评论,原来的问题,而不是张贴这个答案,但StackOverflow不允许这样做。

Just one more thing: I really cannot understand that many times a question is being asked here gets comment with "Why you want to do this" without at least trying to understand the problem. Escpecially when it comes to then need for partial mocking there are really a lot of use cases that I could imagine where it would be useful. That's why the guys from Mockito provided that functionality. This feature should of course not be overused. But when we talk about test case setups that otherwise could not be established in a very complicated way, spying should be used.

在mock中,Spy也支持类的部分mock

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

查看1.10.19和2.7.22文档获得详细解释。

根据文档:

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