使用 Mockito 多次调用具有相同参数的同一方法

问题描述 投票:0回答:14

有没有办法让存根方法在后续调用中返回不同的对象?我想这样做是为了测试来自

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 (...) { }        
}
java mocking mockito
14个回答
933
投票

怎么样

when( method-call ).thenReturn( value1, value2, value3 );

您可以在 thenReturn 的括号中添加任意数量的参数,只要它们都是正确的类型。第一次调用该方法时将返回第一个值,然后返回第二个答案,依此类推。一旦所有其他值都用完,最后一个值将重复返回。


343
投票

您可以使用

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

236
投票

正如之前指出的那样几乎所有的调用都是可链接的。

所以你可以打电话

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 的文档


89
投票

几乎所有的调用都是可链接的:

doReturn(null).doReturn(anotherInstance).when(mock).method();

50
投票

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

取决于参数

选项#1

假设我们有 2 个参数,并检查第二个(列表)参数的大小:

        ...

        when(yourMock.yourMethod(any(), anyList()))
             .thenAnswer(args -> ((List) args.getArgument(1)).size() < 2
                                 ? 1
                                 : 3);

args 是对象,所以我们必须将 arg 转换为我们的类型。在我的例子中,我将 ^^^ 投射到

(List)

选项#2(BDD)

        ...

        given(yourMock.yourMethod(any(), anyList()))
             .willAnswer(args -> ((List) args.getArgument(1)).size() < 2
                                 ? 1
                                 : 3);

5
投票

我已经实现了一个

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

4
投票

doReturn( value1, value2, value3 ).when( 方法调用 )


3
投票

与 @[Igor Nikolaev] 8 年前的回答相关,使用

Answer
可以使用 Java 8 中提供的 lambda 表达式 进行一定程度的简化。

when(someMock.someMethod()).thenAnswer(invocation -> {
    doStuff();
    return;
});

或更简单地说:

when(someMock.someMethod()).thenAnswer(invocation -> doStuff());

2
投票

这与问题没有直接关系。但想把它放在同一个链中。

如果尝试使用多个参数验证同一方法调用,您可以使用 Mockito 的以下时间功能。如果您不验证则不需要它。

Mockito.verify(method, times(n)).methoscall();

这里的“n”是模拟被调用的次数。


2
投票

如果您有动态值列表,则可以使用

AdditionalAnswers.returnsElementsOf
:

import org.mockito.AdditionalAnswers;

when(mock.method()).thenAnswer(AdditionalAnswers.returnsElementsOf(myListOfValues));

2
投票

您可以使用

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

或者也许

LinkedList<Answer<String>> answers = new LinkedList<>();
answers.add(invocation -> "A");
answers.add(invocation -> "B");
answers.add(invocation -> { throw new Exception(); });
when(mock.doSomething(any())).thenAnswer(invocation -> results.removeFirst().apply(invocation));

1
投票

以下可以作为通用方法在不同的方法调用上返回不同的参数。我们唯一需要做的就是传递一个数组,其中包含每次调用中检索对象的顺序。

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


1
投票

这可能是基本/显而易见的,但如果像我一样,您正在尝试模拟对一个方法的多次调用,该方法每次调用要测试的方法时调用次数未知,例如:

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

0
投票

这是 BDD 风格的工作示例,非常简单明了

given(carRepository.findByName(any(String.class))).willReturn(Optional.empty()).willReturn(Optional.of(MockData.createCarEntity()));
© www.soinside.com 2019 - 2024. All rights reserved.