Имитация первого вызова не удалась, второй вызов завершился успешно

119

Я хочу использовать Mockito для тестирования (упрощенного) кода ниже. Я не знаю, как сказать Мокито, что он потерпел неудачу в первый раз, а затем преуспел во второй раз.

for(int i = 1; i < 3; i++) {
  String ret = myMock.doTheCall();

  if("Success".equals(ret)) {
    log.write("success");
  } else if ( i < 3 ) {
    log.write("failed, but I'll try again. attempt: " + i);
  } else {
    throw new FailedThreeTimesException();
  }
}

Я могу настроить тест успеха с помощью:

Mockito.when(myMock).doTheCall().thenReturn("Success");

И тест на отказ:

Mockito.when(myMock).doTheCall().thenReturn("you failed");

Но как я могу проверить, что если он потерпит неудачу один (или два) раза, то все будет хорошо?

ДБ.
источник

Ответы:

250

Из документов :

Иногда нам нужно заглушить другое возвращаемое значение / исключение для одного и того же вызова метода. Типичным вариантом использования может быть имитация итераторов. В исходной версии Mockito не было этой функции для поддержки простого издевательства. Например, вместо итераторов можно использовать Iterable или просто коллекции. Они предлагают естественные способы заглушки (например, использование настоящих коллекций). Однако в редких случаях может быть полезно заглушить последовательные вызовы:

when(mock.someMethod("some arg"))
   .thenThrow(new RuntimeException())
  .thenReturn("foo");

//First call: throws runtime exception:
mock.someMethod("some arg");

//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));

Итак, в вашем случае вам нужно:

when(myMock.doTheCall())
   .thenReturn("You failed")
   .thenReturn("Success");
Джон Скит
источник
25
Это указывало мне в правильном направлении (спасибо) за мой сценарий, который имел дело с методами void - в этом случае вам нужно использовать альтернативный стиль ...doThrow(new RuntimeException()).doNothing().when(myMock).doTheCall();
haggisandchips
38

Самый короткий способ написать то, что вы хотите, - это

when(myMock.doTheCall()).thenReturn("Success", "you failed");

Когда вы предоставляете несколько аргументов, thenReturnкак это, каждый аргумент будет использоваться не более одного раза, за исключением самого последнего аргумента, который используется столько раз, сколько необходимо. Например, в этом случае, если вы сделаете звонок 4 раза, вы получите «Успех», «Вы потерпели неудачу», «Вы потерпели неудачу», «Вы потерпели неудачу».

Дауд ибн Карим
источник
22

Поскольку комментарий, касающийся этого, трудно читать, я добавлю отформатированный ответ.

Если вы пытаетесь сделать это с помощью voidфункции, которая просто генерирует исключение, за которым следует шаг отсутствия поведения, вы должны сделать что-то вроде этого:

Mockito.doThrow(new Exception("MESSAGE"))
            .doNothing()
            .when(mockService).method(eq());
anoneironaut
источник
4

Чтобы добавить к этому и этому ответу, вы также можете использовать цикл для цепочки фиктивных вызовов. Это полезно, если вам нужно смоделировать одно и то же несколько раз или смоделировать какой-то шаблон.

Например (хотя и надуманный):

import org.mockito.stubbing.Stubber;

Stubber stubber = doThrow(new Exception("Exception!"));
for (int i=0; i<10; i++) {
    if (i%2 == 0) {
        stubber.doNothing();
    } else {
        stubber.doThrow(new Exception("Exception"));
    }
}
stubber.when(myMockObject).someMethod(anyString());
typoerrpr
источник
На самом деле я не знал об этом Стаббере и нашел его полезным, +1.
Михай Морков
Это должен быть правильный ответ, если вам нужен вызов метода void.
db80 05
4

У меня другая ситуация, я хотел смоделировать voidфункцию для первого вызова и запустить ее нормально при втором вызове.

Это работает для меня:

Mockito.doThrow(new RuntimeException("random runtime exception"))
       .doCallRealMethod()
       .when(spy).someMethod(Mockito.any());
Мохаммед Эльрашиди
источник