首页 文章

使用Mockito来模拟一些方法而不是其他方法

提问于
浏览
275

有没有办法,使用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);
}

5 回答

  • 18

    要直接回答你的问题,是的,你可以模拟一些方法而不会嘲笑其他方法 . 这称为 partial mock . 有关更多信息,请参见the Mockito documentation on partial mocks .

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

    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() ,否则将模拟每个方法实现 .

    还有一种可能是 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 一样,有一个重要的陷阱,如上例所示 . 将调用真实方法(因为在运行时 when(..) 之前评估 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
    

    但是,在你的例子中,我相信它仍然会失败,因为 getValue() 的实现依赖于 quantityprice ,而不是 getQuantity()getPrice() ,这就是你所嘲笑的 .

    你真正想要的只是:

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

    通过模拟中的 Spy 也支持对类进行部分模拟

    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文档 .

  • 15

    根据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();
    
  • -2

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

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

    根据文档,你想要的是 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);
        }
    }
    
  • 116

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

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

    还有一件事:我真的无法理解,在这里被问到的问题多次被评论为“为什么你想这样做”而不至少试图理解问题 . 特别是当需要进行部分模拟时,我确实会想到很多用例 . 这就是Mockito的人提供这种功能的原因 . 这个功能当然不应该被滥用 . 但是当我们谈论测试用例设置时,否则无法以非常复杂的方式 Build ,应该使用 Spy .

相关问题