8

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

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

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

  1. 用 mockito 实现这种模拟的最简单方法是什么?
  2. 我可以实现附加要求:调用nextItem()第二次、第三次等会导致特定类型的异常吗?

PS我不想为测试提供我的接口的完整实现,因为其中有其他方法,导致代码难以理解和冗长。

4

6 回答 6

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;
 }
});
于 2012-09-29T21:56:03.780 回答
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)表示:在第一次调用和第二次返回falsetrueInOrder对象用于验证调用顺序。更改顺序或删除nextItem()呼叫,测试将失败。

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

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

如果需要更强大的 mocking 语义,可以引入重炮——自定义应答回调:

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();
        }
    }
});

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

最后,您可以通过仅模拟选定的方法来窥探您的真实对象

于 2012-09-29T21:18:58.797 回答
2

您可以提供一些自定义 Answer 实现,其中一个依赖于另一个:

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);

您可能会进行调整,因为我尚未测试此代码,但该方法应该是您所需要的。

于 2012-09-29T21:58:41.370 回答
2

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

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

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

A MockQueue 将使您的测试比此处给出的其他(但正确的)响应更具可读性。

于 2012-10-01T12:46:10.057 回答
1

您可以使用mockito 文档中描述的技术告诉 mockito 在对同一模拟方法的连续调用时以不同方式回答。

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

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

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

nextItem()在第一次调用时返回一些东西,并在以后的调用中抛出异常。

我不知道是否有可能使其中一种方法的结果取决于对另一种方法的调用顺序。如果确实有可能,我敢肯定它要复杂得多。

于 2012-09-29T21:24:42.873 回答
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 风格”。

于 2016-10-21T14:01:01.533 回答