Mockito:方法的返回值取决于其他方法

时间:2012-09-29 21:04:22

标签: java junit mockito

在我的单元测试中,我需要模拟一个接口,该接口在不同方法中具有nextItem()isEmpty()方法:

public interface MyQueue {
    Item nextItem();
    boolean isEmpty();
    //other methods
    ...
}

我对模拟的要求是isEmpty()最初应该返回false,但是在调用nextItem()之后isEmpty()应该返回true。因此,我正在用一个项目嘲笑一个队列。

  1. 用mockito实现这种模拟的最简单方法是什么?
  2. 我可以实现其他要求:第二次调用nextItem(),第三次等等会导致特定类型的异常吗?
  3. P.S。我不想为测试提供我的接口的完整实现,因为其中有其他方法,导致难以理解和冗长的代码。

6 个答案:

答案 0 :(得分:9)

你可以用thenAnswer()实现这一点,Mockito文档认为这个功能有争议:

另一个有争议的功能,原本没有包含在Mockito中。我们建议仅使用toReturn()或toThrow()的简单存根。这两个应该足以测试/测试任何干净的&简单的代码。

接下来是答案:

private boolean called = false;

when(mock.nextItem()).thenAnswer(new Answer() {
 Object answer(InvocationOnMock invocation) {   
     called = true;       
     return item;
 }
when(mock.isEmpty()).thenAnswer(new Answer() {
 Object answer(InvocationOnMock invocation) {          
     return called;
 }
});

答案 1 :(得分:6)

这是一个简单的例子:

//given
MyQueue mock = mock(MyQueue.class);

given(mock.isEmpty()).willReturn(false, true);
given(mock.nextItem()).willReturn(someItem);

//when
mock.isEmpty();   //yields false
mock.nextItem();  //yields someItem
mock.isEmpty();   //yields true

//then
InOrder inOrder = inOrder(mock);
inOrder.verify(mock).isEmpty();
inOrder.verify(mock).nextItem();
inOrder.verify(mock).isEmpty();

willReturn(false, true)表示:在第一次调用时返回false,在第二次时返回trueInOrder对象用于验证调用顺序。更改订单或删除nextItem()电话,测试将失败。

或者您可以使用以下语法:

given(mock.isEmpty()).
        willReturn(false).
        willReturn(true).
        willThrow(SpecialException.class);

如果你需要更强大的嘲讽语义,你可以引入重炮 - 自定义答案回调:

given(mock.isEmpty()).willAnswer(new Answer<Boolean>() {
    private int counter = 0;
    @Override
    public Boolean answer(InvocationOnMock invocation) throws Throwable {
        switch(++counter) {
            case 1: return false;
            case 2: return true;
            default: throw new SpecialException();
        }
    }
});

但这很容易导致无法维护的测试代码,请谨慎使用。

最后,您可以通过仅模拟选定的方法来spy your real object

答案 2 :(得分:2)

您可以提供一些自定义的答案实施,其中一个取决于另一个:

public class NextItemAnswer implements Answer<Item> {
   private int invocationCount = 0;
   private Item item;
   public NextItemAnswer(Item item) {
       this.item = item;
   }

   public Item answer(InvocationOnMock invocation) throws Throwable {
       invocationCount++;
       return item;
   }

   public int getInvocationCount() {
       return invocationCount;
   }
}

public class IsEmptyAnswer implements Answer<Boolean> {
   private NextItemAnswer nextItemAnswer;
   public IsEmptyAnswer(NextItemAnswer nextItemAnswer) {
       this.nextItemAnswer = nextItemAnswer;
   }
   public Boolean answer(InvocationOnMock invocation) throws Throwable {
       return nextItemAnswer.getInvocationCount() >= 0;
   }
}

然后使用它:

NextItemAnswer nextItemAnswer = new NextItemAnswer(item);
IsEmptyAnswer isEmptyAnswer = new IsEmptyAnswer(nextItemAnswer);

when(mock.isEmpty()).thenAnswer(isEmptyAnswer);
when(mock.nextItem()).thenAnswer(nextItemAnswer);

你可能会调整,因为我没有测试过这段代码,但这种方法应该是你需要的。

答案 3 :(得分:2)

我确实意识到你明确写道,你不想提供MyQueue的完整实现,但说实话,这是我要做的第一件事。

事实上,我经常提供相当复杂的接口/对象的“模拟”实现,以便使测试更容易测试。我并不是唯一一个这样认为的人:例如,Spring Framework提供了许多模拟版本的复杂对象(MockHttpServletRequest,MockHttpServletResponse等)。

在这种情况下,我会避免混乱我的测试,并在单独的包中或甚至在生产代码中提供此类。

MockQueue会使你的测试比这里给出的其他(但正确的)响应更具可读性。

答案 4 :(得分:1)

你可以告诉mockito使用the mockito docs中描述的技术对连续调用同一个模拟方法做出不同的回答。

when(mock.isEmpty())
  .thenReturn(false)
  .thenReturn(true);

只会在第一次调用时使isEmpty()调用返回true,并且

when(mock.nextItem())
  .thenReturn(item)
  .thenThrow(new NextOnEmptyQueueException())

会使nextItem()在第一次调用时返回一些内容并在以后的调用中抛出异常。

我不知道是否有可能使其中一种方法的结果依赖于对另一种方法的调用顺序。如果它确实可行,我相信它会变得更复杂。

答案 5 :(得分:1)

您可以制作实用工具方法,然后在任何地方使用它。

public static boolean mockHasInvocation(Object mock, String methodName, Object... args) {
    return mockingDetails(mock).getInvocations().stream()
            .anyMatch(o -> o.getMethod().getName().equals(methodName) && Arrays.equals(o.getArguments(), args));
}

简单用法:

if(mockHasInvocation(mockObject, "methodName", "argument1", "argument2")){doSomething();}

在这种情况下,您不需要任何其他变量,而且它更像是“Mockito风格”。