it-swarm.com.ru

Моделирование сервиса с использованием Mockito 2 приводит к ошибке заглушки

Я пытаюсь смоделировать поведение класса, используя Mockito . Это работает с использованием Mockito 1.x. Переход на JUnit 5 и Mockito 2, похоже, больше не работает.

@ExtendWith(MockitoExtension.class)
public class MockitoExample {

  static abstract class TestClass {
    public abstract int booleanMethod(boolean arg);
  }

  @Mock
  TestClass testClass;

  @BeforeEach
  public void beforeEach() {
    when(testClass.booleanMethod(eq(true))).thenReturn(1);
    when(testClass.booleanMethod(eq(false))).thenReturn(2);
  }

  @Test
  public void test() {
    assertEquals(1,testClass.booleanMethod(true));
    assertEquals(2,testClass.booleanMethod(false));
  }
}

Ожидается, что фиктивный TestClass показывает поведение, протестированное в тестовом методе.

Я получаю ошибку:

org.mockito.exceptions.misusing.PotentialStubbingProblem: 

  Strict stubbing argument mismatch. Please check:
   - this invocation of 'booleanMethod' method:
      testClass.booleanMethod(false);
      -> at org.oneandone.ejbcdiunit.mockito_example.MockitoExample.beforeEach(MockitoExample.Java:30)
   - has following stubbing(s) with different arguments:
      1. testClass.booleanMethod(false);
        -> at org.oneandone.ejbcdiunit.mockito_example.MockitoExample.beforeEach(MockitoExample.Java:29)
  Typically, stubbing argument mismatch indicates user mistake when writing tests.
  Mockito fails early so that you can debug potential problem easily.
  However, there are legit scenarios when this exception generates false negative signal:
    - stubbing the same method multiple times using 'given().will()' or 'when().then()' API
      Please use 'will().given()' or 'doReturn().when()' API for stubbing.
    - stubbed method is intentionally invoked with different arguments by code under test
      Please use default or 'silent' JUnit Rule (equivalent of Strictness.LENIENT).
  For more information see javadoc for PotentialStubbingProblem class.

В обоих случаях аргумент false, кажется, совпадает, хотя я явно соответствовал true.

Это ошибка в Mockito 2.17 или недоразумение. Как/можно использовать Mockito 2.x для имитации вызовов с разными логическими аргументами?

пример также можно найти на github. Но верный запуск теста начнется только с использованием 

mvn test -Dtest=MockitoExample

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

4
aschoerk

При строгих заглушках (поведение Mockito по умолчанию) вызов нескольких when для одного и того же метода сбрасывает эту имитацию. Решение состоит в том, чтобы вызвать when один раз и иметь логику в Answer:

@BeforeEach
public void beforeEach() {
    when(testClass.booleanMethod(anyBoolean())).thenAnswer(invocationOnMock -> {
        if ((boolean) invocationOnMock.getArguments()[0]) {
            return 1;
        }
        return 2;
    });
}

В качестве альтернативы вы можете использовать снисходительный макет, но это не всегда хорошая идея - снисходительный макет позволяет использовать избыточные заглушки и упрощает ошибки в тесте, что может привести к незамеченным ошибкам в «производственном» коде:

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class MockitoExample {
7
Mureinik

Начиная с Mockito 2.20, можно также добавить lenient () локально

@ExtendWith(MockitoExtension.class)
public class MockitoExample {

  static abstract class TestClass {
    public abstract int booleanMethod(boolean arg);
  }

  @Mock
  TestClass testClass;

  @BeforeEach
  public void beforeEach() {
    lenient().when(testClass.booleanMethod(eq(true))).thenReturn(1);
    lenient().when(testClass.booleanMethod(eq(false))).thenReturn(2);
  }

  @Test
  public void test() {
    assertEquals(1,testClass.booleanMethod(true));
    assertEquals(2,testClass.booleanMethod(false));
  }
}
4
aschoerk

Мокито 1 и 2 не имеют одинакового уровня "строгости".
Кроме того, при использовании Mockito 2 с JUnit 4 или 5 уровень по умолчанию все равно будет другим. 

Подводить итоги :

3 уровня строгости: 

  • LENIENT: минимальная строгость
  • WARN: дополнительные предупреждения выводятся на консоль
  • STRICT_STUBS: обеспечивает чистые тесты, выдавая исключение в случае потенциального неправильного использования, но также может привести к ложным срабатываниям.

Эффективный уровень по умолчанию в соответствии с используемыми API: 

  • Mockito 1: LENIENT 
  • Mockito 2 с JUnit 4: WARN
  • Mockito 2 с JUnit 5 (MockitoExtension.class): STRICT_STUBS 
  • Mockito 3: планируется быть STRICT_STUBS.

Подробнее

Фактическая документация Mockito очень ясно говорит об этом:

Strictness javadoc сообщает: 

Настраивает "строгость" Mockito во время сеанса насмешки. A сеанс обычно отображается на один вызов метода тестирования. Строгость проводит чистые тесты и лучшую производительность. Самый простой способ усилить строгость использует поддержку MUKITO JUnit (MockitoRule или MockitoJUnitRunner). Если вы не можете использовать поддержку JUnit MockitoSession - это путь.

Как уровень строгости влияет на поведение теста (издевательство Сессия)?

1 .Strictness.LENIENT - без дополнительного поведения. По умолчанию Mockito 1.x. Рекомендуется, только если вы не можете использовать STRICT_STUBS или WARN.

2 .Strictness.WARN - помогает поддерживать чистоту тестов и улучшает возможность отладки. Сообщает о предупреждениях консоли о неиспользуемых заглушках и заглушках несоответствие аргумента (см. org.mockito.quality.MockitoHint). По умолчанию поведение Mockito 2.x при использовании JUnitRule или MockitoJUnitRunner . Рекомендуется, если вы не можете использовать STRICT_STUBS.

3 .Strictness.STRICT_STUBS - обеспечивает чистые тесты, уменьшает дублирование тестового кода, улучшает возможность отладки. Лучшее сочетание гибкости и производительность. Настоятельно рекомендуется. Запланировано по умолчанию для Mockito v3.Смотрите STRICT_STUBS для деталей.

Но какое бы то ни было выброшенное исключение, связанное с сообщением 

"имеет следующие заглушки с разными аргументами"

кажется чересчур строгой проверкой. Сообщение об исключении в некотором роде доказывает это: 

Однако существуют законные сценарии, когда это исключение создает false отрицательный сигнал:

...

  • метод stubbed преднамеренно вызывается с разными аргументами тестируемым кодом

Так что запретить его по умолчанию кажется слишком много.
Таким образом, если вы используете JUnit 5, в качестве альтернативы STRICT_STUBS вы можете использовать WARNING, но обычно вы хотите избежать LENIENT, который слишком тихий. 

В дополнение к MockitoExtension библиотека mockito-junit-jupiter предоставляет код @MockitoSettings, который можно использовать как на уровне метода, так и на уровне класса. 

Вот пример: 

import Java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;

@ExtendWith(MockitoExtension.class)
public class FooTest {

    @MockitoSettings(strictness = Strictness.WARN)
    @Test
    void foo() throws Exception {
        List<String> strings = Mockito.mock(List.class);
        Mockito.when(strings.add("a"))
               .thenReturn(true);
        Mockito.when(strings.add("b"))
               .thenReturn(false);
    }

    @Test
    void fooKo() throws Exception {
        List<String> strings = Mockito.mock(List.class);
        Mockito.when(strings.add("a"))
               .thenReturn(true);
        Mockito.when(strings.add("b"))
               .thenReturn(false);

    }

}

fooKo() генерирует исключительную ситуацию Mockito для неправильного использования, в то время как foo() успешен, но выдает полезные предупреждения: 

 [MockitoHint] FooTest (см. Javadoc для MockitoHint): 
 [MockitoHint] 1. Не используется -> на FooTest.foo (FooTest.Java:19) 
 [MockitoHint] 2. Не используется -> на FooTest. Foo (FooTest.Java:21) 

В качестве другой альтернативы вы также можете использовать Mockito.lenient(), очень хорошо описанную Aschoerk, чтобы применить снисходительную строгость к конкретному вызову. Кроме того, вы можете установить все ложные вызовы как снисходительные при имитации экземпляра:

@Test
void foo() throws Exception {
    List<String> strings = Mockito.mock(List.class, Mockito.withSettings()
                                                           .lenient());
     ....
}
0
davidxxx