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


402

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

4
你为什么想这么做?您应该测试该类(在这种情况下,根本不应该进行模拟),或者您应该在测试不同的类时对它进行模拟(在这种情况下,没有功能)。为什么要进行部分模拟?
weltraumpirat

3
好的,这是一个真实的例子。实际上,我试图通过传递人为的值来避免对数据库的调用,但我想验证其他方法是否可以正确地使用这些人为的值。有一个更好的方法吗?
Victor Grazi 2013年

5
当然:将数据库调用移到一个单独的类(域逻辑和数据库访问不应在同一类中;这是两个不同的问题),提取其接口,使用该接口从域逻辑类进行连接,并仅模拟测试过程中的界面。
weltraumpirat

1
我完全同意,如果不在此处上载包括第三方库在内的大量代码,就很难解释整个图片。
Victor Grazi 2013年

1
您可能可以。但是,那不是“更好的方法”:数据库代码是您想从应用程序其余部分中隐藏的实现细节,甚至可能移至其他程序包。您不想每次更改续集语句都必须重新编译域逻辑,对吗?
weltraumpirat

Answers:


642

要直接回答您的问题,是的,您可以模拟某些方法而无需模拟其他方法。这称为部分模拟。有关更多信息,请参见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

在这种情况下,除非thenCallRealMethod()when(..)子句中指定,否则将模拟每个方法的实现。

还有一种可能是使用间谍而不是模拟来代替间谍

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)像前面的示例一样,与间谍一起使用时,有一个重要的陷阱。真正的方法将被调用(因为在运行时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()and 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);
}

21
我认为这个答案是错误的。您需要SPY对象的实例,而不是MOCK该类。
加勒佩塔2014年

2
@GaRRaPeTa我想说间谍和嘲笑都是合理的选择。很难说哪种方法最适合这种情况,因为OP指出这是一个简化的示例。
乔恩·纽缪斯

1
应该不是“间谍”而不是“模拟”,因为“间谍”以更好的方式提供了部分嘲笑。
塔伦·萨普拉

2
Stock stock = spy(Stock.class);这似乎是错误的,spy方法似乎只接受对象而不是类。
Paramvir Singh Karwal,

4
+1指出doReturn(retval).when(spyObj).methodName(args)和之间的区别when(spyObj.methodName(args)).thenReturn(retval)
Captain_Obvious

140

通过Mito中的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文档以获取详细说明。


37

根据文档

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
感谢您演示如何设置模拟,在该模拟中,除我需要从测试中控制的几种方法外,所有方法均需调用真实的实现。
bigh_29 2015年

class NaughtyLinkedList extends LinkedList { public int size() { throw new RuntimeException("don't call me");} } @Test public void partialMockNaughtLinkedList(){ List mock = mock(NaughtyLinkedList.class, CALLS_REAL_METHODS); mock.add(new Object()); // this calls the real function when(mock.size()).thenReturn(2); // For whatever reason, this lines throws the RuntimeException. assertEquals(2,mock.size()); }这行不通。无论出于何种原因,当执行“何时”时,它实际上都会执行应该被模拟的方法。码:
兰斯亲切

3
问题是“何时”。实际上,“何时”将执行您要部分模拟的东西。为避免这种情况,有一个替代方法:doReturn()。请参阅doc.mockito.googlecode.com/hg/1.9.5/org/mockito/…上的doReturn()
Lance Kind

18

您想要的是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.)
 */

1
很好发现...但是我只能问你为什么这样使用withSettings()...吗?似乎org.mockito.internal.stubbing.answers.CallsRealMethods()(例如)可以完成工作……而此类的javadoc专门说它用于部分模拟……
mike rodent

3
另外...这不会碰到其他答案在这里遇到的问题:即thenReturn实际上将执行该方法(这可能会导致问题,尽管在本示例中不是),因此doReturn在这种情况下更可取...?
mike啮齿动物

4

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

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

再说一件事:我真的无法理解,很多问题在这里被问到“为什么要这么做”而至少没有试图理解这个问题。特别是当涉及到需要部分模拟的情况时,我确实可以想象到很多用例在什么地方有用。这就是Mockito的家伙提供该功能的原因。当然,不应过度使用此功能。但是,当我们谈论无法通过非常复杂的方式建立的测试用例设置时,应该使用间谍程序。


2
我觉得这个答案部分是一种意见。请考虑编辑。
听起来像是

2
提议为家庭中的新成员加油。无需在-ve区域中获取此内容,那里在技术上没有真正的错误或不正确的语言/音调。善待新成员。谢谢。
萨拉·帕蒂(Saurabh Patil),
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.