使用 Mockito 多次调用具有相同参数的相同方法
Posted
技术标签:
【中文标题】使用 Mockito 多次调用具有相同参数的相同方法【英文标题】:Using Mockito with multiple calls to the same method with the same arguments 【发布时间】:2022-01-14 19:11:37 【问题描述】:有没有办法让存根方法在后续调用中返回不同的对象?我想这样做来测试来自ExecutorCompletionService
的不确定响应。即测试无论方法的返回顺序如何,结果都保持不变。
我要测试的代码看起来像这样。
// Create an completion service so we can group these tasks together
ExecutorCompletionService<T> completionService =
new ExecutorCompletionService<T>(service);
// Add all these tasks to the completion service
for (Callable<T> t : ts)
completionService.submit(request);
// As an when each call finished, add it to the response set.
for (int i = 0; i < calls.size(); i ++)
try
T t = completionService.take().get();
// do some stuff that I want to test
catch (...)
【问题讨论】:
【参考方案1】:您可以使用thenAnswer
方法来做到这一点(与when
链接时):
when(someMock.someMethod()).thenAnswer(new Answer()
private int count = 0;
public Object answer(InvocationOnMock invocation)
if (count++ == 1)
return 1;
return 2;
);
或使用等效的静态doAnswer
方法:
doAnswer(new Answer()
private int count = 0;
public Object answer(InvocationOnMock invocation)
if (count++ == 1)
return 1;
return 2;
).when(someMock).someMethod();
【讨论】:
这个答案对我帮助很大,因为doAnswer()
/thenAnswer()
不允许像doReturn()
/thenReturn()
那样链接多个调用,我需要计算一些东西而不仅仅是返回一个不同的值.使用私有 count
变量创建匿名 Answer
对象对我来说是诀窍。
请记住,当someMethod()
返回void
时,这些是不等价的。有关详细信息,请参阅this 答案。【参考方案2】:
怎么样
when( method-call ).thenReturn( value1, value2, value3 );
您可以在 thenReturn 的括号中放置任意数量的参数,只要它们都是正确的类型。第一次调用该方法时将返回第一个值,然后是第二个答案,依此类推。一旦所有其他值都用完,最后一个值将重复返回。
【讨论】:
这适用于模拟,但不适用于间谍。如果需要阻止调用原始方法,则需要 doAnswer(...).when(someSpy).someMethod(...)。 @Yuri - 不完全是。在您提到的情况下,您不需要doAnswer
或写Answer
。你可以使用doReturn(...).when(someSpy).someMethod(...)
。假设艾玛对模拟而不是间谍感兴趣似乎是合理的,但我想我可以在我的答案中添加一些内容来说明这一点。感谢您的评论。
@DawoodibnKareem 假设第一次调用我想返回一个值,第二次调用我想抛出一个异常。如何做到这一点?
@Rito 请阅读 Volodymyr 的回答或 Raystorm 的回答。他们都涵盖了那个案例。
这样一个光荣的答案。【参考方案3】:
几乎所有的调用都是可链接的:
doReturn(null).doReturn(anotherInstance).when(mock).method();
【讨论】:
【参考方案4】:作为previously pointed out,几乎所有的调用都是可链接的。
所以你可以打电话
when(mock.method()).thenReturn(foo).thenReturn(bar).thenThrow(new Exception("test"));
//OR if you're mocking a void method and/or using spy instead of mock
doReturn(foo).doReturn(bar).doThrow(new Exception("Test").when(mock).method();
Mockito's Documenation 中的更多信息。
【讨论】:
非常有帮助!在这个例子中第四次调用mock.method
会发生什么?我想要类似的东西,第一次返回 foo 但其余的都返回 bar。
对 mock 的每次额外调用都会返回最后一个 'thenReturn' 或最后一个 'thenThrow' 非常有用
感谢您提供的伟大而简单的指示。直到现在才知道这一点。我一直在努力寻找如何在两个相同的电话中取回两个不同的结果。为我节省大量时间。
优秀的解决方案!使用这个。【参考方案5】:
以下可以作为常用方法在不同的方法调用上返回不同的参数。我们唯一需要做的就是传递一个数组,其中包含在每次调用中检索对象的顺序。
@SafeVarargs
public static <Mock> Answer<Mock> getAnswerForSubsequentCalls(final Mock... mockArr)
return new Answer<Mock>()
private int count=0, size=mockArr.length;
public Mock answer(InvocationOnMock invocation) throws throwable
Mock mock = null;
for(; count<size && mock==null; count++)
mock = mockArr[count];
return mock;
例如。 getAnswerForSubsequentCalls(mock1, mock3, mock2);
将在第一次调用时返回 mock1 对象,在第二次调用时返回 mock3 对象,在第三次调用时返回 mock2 对象。
应该像when(something()).doAnswer(getAnswerForSubsequentCalls(mock1, mock3, mock2));
一样使用
这几乎类似于when(something()).thenReturn(mock1, mock3, mock2);
【讨论】:
【参考方案6】:我已经实现了一个MultipleAnswer
类,它可以帮助我在每次通话中存根不同的答案。这是一段代码:
private final class MultipleAnswer<T> implements Answer<T>
private final ArrayList<Answer<T>> mAnswers;
MultipleAnswer(Answer<T>... answer)
mAnswers = new ArrayList<>();
mAnswers.addAll(Arrays.asList(answer));
@Override
public T answer(InvocationOnMock invocation) throws Throwable
return mAnswers.remove(0).answer(invocation);
【讨论】:
你能用一种简短易读的方式初始化那个对象吗?【参考方案7】:与 8 年前 @[Igor Nikolaev] 的回答相关,使用 Answer
可以使用 Java 8 中的 lambda expression 进行一些简化。
when(someMock.someMethod()).thenAnswer(invocation ->
doStuff();
return;
);
或更简单地说:
when(someMock.someMethod()).thenAnswer(invocation -> doStuff());
【讨论】:
【参考方案8】:BDD 风格:
import static org.mockito.BDDMockito.given;
...
given(yourMock.yourMethod()).willReturn(1, 2, 3);
经典款式:
import static org.mockito.Mockito.when;
...
when(yourMock.yourMethod()).thenReturn(1, 2, 3);
显式风格:
...
when(yourMock.yourMethod())
.thenReturn(1)
.thenReturn(2)
.thenReturn(3);
【讨论】:
【参考方案9】:doReturn( value1, value2, value3 ).when( 方法调用 )
【讨论】:
你的意思是doReturn(value1, value2, value3).when(mock).methodCall()
可能吗?【参考方案10】:
这是 BDD 风格的工作示例,非常简单明了
given(carRepository.findByName(any(String.class))).willReturn(Optional.empty()).willReturn(Optional.of(MockData.createCarEntity()));
【讨论】:
【参考方案11】:您可以使用LinkedList
和Answer
。例如
MyService mock = mock(MyService.class);
LinkedList<String> results = new LinkedList<>(List.of("A", "B", "C"));
when(mock.doSomething(any())).thenAnswer(invocation -> results.removeFirst());
【讨论】:
【参考方案12】:这与问题没有直接关系。但想把它放在同一个链中。
如果尝试使用多个参数验证相同的方法调用,您可以使用 Mockito 的以下时间功能。如果您不进行验证,则不需要它。
Mockito.verify(method, times(n)).methoscall();
这里的“n”是调用模拟的次数。
【讨论】:
【参考方案13】:如果你有一个动态的值列表,你可以使用AdditionalAnswers.returnsElementsOf
:
import org.mockito.AdditionalAnswers;
when(mock.method()).thenAnswer(AdditionalAnswers.returnsElementsOf(myListOfValues));
【讨论】:
【参考方案14】:这可能是基本的/显而易见的,但如果像我一样,你试图模拟一个方法的多次调用,每次调用要测试的方法被调用的次数未知,例如:
public String method(String testArg)
//...
while(condition)
someValue = someBean.nestedMethod(); // This is called unknown number of times
//...
//...
你可以这样做:
@Test
public void testMethod()
mockNestedMethodForValue("value1");
assertEquals(method("arg"), "expected1");
mockNestedMethodForValue("value2");
assertEquals(method("arg"), "expected2");
mockNestedMethodForValue("value3");
assertEquals(method("arg"), "expected3");
private void mockNestedMethodForValue(String value)
doReturn(value).when(someBeanMock).nestedMethod();
【讨论】:
以上是关于使用 Mockito 多次调用具有相同参数的相同方法的主要内容,如果未能解决你的问题,请参考以下文章
为啥 ListView onScroll() 被多次调用,每次都使用相同的参数量?
在一个http请求中多次调用相同的GraphQL突变操作[重复]