关于java:使用Mockito来模拟一些方法而不是其他方法

Use Mockito to mock some methods but not others

有没有办法,使用mockito,在一个类中模拟一些方法,而不是其他方法?

例如,在这个(公认是人为设计的)股票类中,我想模拟getprice()和getquantity()返回值(如下面的测试片段所示),但我希望getvalue()执行股票类中编码的乘法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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文档。

对于您的示例,您可以在测试中执行如下操作:

1
2
3
4
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()

还有一种可能是用另一种方法对付间谍而不是嘲笑:

1
2
3
4
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-like时,有一个重要的陷阱。将调用real方法(因为运行时在when(..)之前对stock.getPrice()进行了计算)。如果方法包含不应调用的逻辑,则这可能是一个问题。您可以这样编写前面的示例:

1
2
3
4
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

但是,以你的例子来说,我相信它仍然会失败,因为getValue()的实施依赖于quantityprice,而不是你所嘲笑的getQuantity()getPrice()

你真正想要的是:

1
2
3
4
5
6
@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中,通过间谍也支持对类的部分模拟。

1
2
3
4
5
6
7
8
9
10
11
12
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文件,了解详细解释。


根据文件:

1
2
3
4
5
6
7
8
9
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();


根据问题,接受的答案不正确。

Stock stock = mock(Stock.class);的调用调用org.mockito.Mockito.mock(Class)如下:

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

价值RETURNS_DEFAULTS的文件说明:

1
2
3
4
5
6
7
8
9
10
11
/**
 * The default <wyn>Answer</wyn> of every mock if 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.)
 */

根据文件,您需要的是org.mockito.Mockito.CALLS_REAL_METHODS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
 * Optional <wyn>Answer</wyn> 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 the partial mock warning:
 * 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:
 * [cc lang="java"]<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();
 *

*/< /代码>

因此,您的代码应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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);
    }
}


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

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

还有一件事:我真的不明白,很多时候这里都会有人问一个问题,"为什么你要这样做",但至少要试着去理解这个问题。特别是当需要进行部分模拟时,我可以想象到在哪里它会有用。这就是为什么Mockito的人提供了这个功能。当然,这个特性不应该被过度使用。但是,当我们谈论测试用例设置时,如果不是以一种非常复杂的方式建立的话,就应该使用间谍。