使用 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】:

您可以使用LinkedListAnswer。例如

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 多次调用具有相同参数的相同方法的主要内容,如果未能解决你的问题,请参考以下文章

mock对象返回具有相同参数的真实对象

为啥 ListView onScroll() 被多次调用,每次都使用相同的参数量?

在一个http请求中多次调用相同的GraphQL突变操作[重复]

应用程序脚本缓慢调用多次调用具有多个范围的相同函数,它调用 getValues

ios:多次使用相同的sqlite参数会导致过早的内存释放

Celery/Redis 相同的任务被并行执行多次