使用Mockito模拟某些方法,而不是其他方法

是否有任何方法,使用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);
}
608912 次浏览

为了直接回答您的问题,是的,您可以模拟一些方法而不模拟其他方法。这被称为部分模拟。有关更多信息,请参阅关于部分模拟的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(..)子句中指定thenCallRealMethod()

也有可能反过来用间谍代替模拟:

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()when(..)之前被求值)。如果您的方法包含不应该调用的逻辑,这可能会成为一个问题。你可以这样写上面的例子:

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()的实现依赖于quantityprice,而不是您所嘲笑的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);
}

类的部分模拟也可以通过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.192.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();

你想要的是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(Class<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不允许这样做。

还有一件事:我真的不能理解,在这里问一个问题的时候,很多次都得到了“为什么你想这样做”的评论,而不去尝试理解问题。特别是当涉及到部分模拟的需求时,我可以想象它在很多用例中是有用的。这就是为什么Mockito的人提供了这个功能。当然,这个特性不应该被过度使用。但是当我们谈论测试用例设置时,否则就不能以非常复杂的方式建立,间谍应该被使用。