Использование Mockito с несколькими вызовами одного и того же метода с одинаковыми аргументами


290

Есть ли способ заставить заглушенный метод возвращать разные объекты при последующих вызовах? Я хотел бы сделать это, чтобы проверить неопределенные ответы от 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 (...) { }        
}

Ответы:


255

Вы можете сделать это, используя 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();

637

Как насчет

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

Вы можете указать столько аргументов, сколько захотите, в скобках thenReturn, при условии, что все они имеют правильный тип. Первое значение будет возвращено при первом вызове метода, затем второй ответ и так далее. Последнее значение будет возвращено повторно, как только все остальные значения будут использованы.


4
Это будет работать с макетом, но не со шпионом. Если вам нужно предотвратить вызов исходного метода, вам нужно сделать doAnswer (...). When (someSpy) .someMethod (...).
Юрий

6
@Yuri - не совсем. Вам не нужно doAnswerили написать Answerв случае, что вы упоминаете. Вы можете просто использовать doReturn(...).when(someSpy).someMethod(...). Кажется разумным предположить, что Эмма интересуется насмешками, а не шпионами, но я думаю, я мог бы добавить кое-что к своему ответу, чтобы объяснить это. Спасибо за комментарий.
Дауд ибн Карим

@DawoodibnKareem, скажем, для первого вызова я хочу вернуть значение, а для второго вызова я хочу вызвать исключение. Как это может быть сделано?
Рито

@Rito Пожалуйста, прочитайте ответ Владимира или Raystorm ответ. Они оба охватывают этот случай.
Дауд ибн Карим

Такой славный ответ.
wild_nothing

151

Как указывалось ранее, почти все звонки являются связными.

Чтобы вы могли позвонить

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

Больше информации в Документации Мокито .


3
Очень полезно! Что произойдет, 4-й раз mock.methodбыл назван в этом примере? Я хочу что-то вроде, вернуть foo в первый раз, но вернуть бар для всех остальных.
javaPlease42

6
Каждый дополнительный вызов в макете будет возвращать последний «thenReturn» или последний «thenThrow». Очень полезно
Francois Lacoursiere

Спасибо за отличные и простые инструкции. Никогда не знал этого до сих пор. Я изо всех сил пытался найти, как вернуть два разных результата на два одинаковых вызова. Сэкономь мне кучу времени.
CharlesC


4

Я реализовал 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);
    }
}

1
Можете ли вы инициализировать этот объект коротким, простым и читаемым способом?
usr-local-ΕΨΗΕΛΩΝ

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

Ex. getAnswerForSubsequentCalls(mock1, mock3, mock2);вернет объект mock1 при первом вызове, объект mock3 при втором вызове и объект mock2 при третьем вызове. Должен использоваться как when(something()).doAnswer(getAnswerForSubsequentCalls(mock1, mock3, mock2)); Это почти похоже наwhen(something()).thenReturn(mock1, mock3, mock2);


1

Относительно ответа @ [Igor Nikolaev] от 8 лет назад, использование an Answerможет быть несколько упрощено с помощью лямбда-выражения, доступного в Java 8.

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

или проще:

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

1

BDD стиль:

import static org.mockito.BDDMockito.*;
...
    @Test
    void submit() {
        given(yourMock.yourMethod()).willReturn(1, 2, 3);

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.