Mockito проверить после исключения Junit 4.10

Я тестирую метод с ожидаемым исключением. Мне также нужно проверить, что был вызван какой-то код очистки (на издеваемом объекте) после исключения, но похоже, что эта проверка игнорируется. Вот код. Я использую Junit ExpectedException Rule для проверки ожидаемого исключения.

@Rule
public ExpectedException expectedEx = ExpectedException.none();

@Test
public void testExpectedException()
{
   MockedObject mockObj = mock(MockedObj.class);
   MySubject subject = new MySubject(mockedObj);
   expectedEx.expect(MyException.class);
   expectedEx.expectMessage("My exception message.");
   subject.someMethodThrowingException();
   verify(mockObj).
       someCleanup(eq(...));
}

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

Любая идея, почему это происходит?

Ответ 1

ExpectedException работает, оборачивая весь ваш тестовый метод в блок try-catch через JUnit @Rule. Когда ваш код генерирует исключение, он поднимается по стеку до ближайшей попытки/уловки, что происходит в экземпляре ExpectedException (который проверяет, что это ожидаемое вами исключение).

В Java, если в методе возникает неперехваченное исключение, элемент управления никогда не вернется к операторам позже в этом методе. Здесь применяются те же правила: Control никогда не возвращается к операторам в вашем тесте после исключения.

Технически вы можете поместить проверки в окончательный блок, но это, как правило, плохая привычка. РЕДАКТИРОВАТЬ: ваша тестируемая система может выдать непредвиденное исключение или вообще никакого исключения, которое даст вам полезное сообщение об ошибке и трассировку; однако, если после этого сбоя ваши проверки или утверждения потерпят неудачу в блоке finally, Java покажет это, а не сообщение о неожиданном исключении или неожиданном успехе. Это может затруднить отладку, особенно из-за того, что ваша ошибка возникнет из строк кода, следующих за основной причиной ошибки, что неверно означает, что приведенный выше код завершился успешно.

Если вам действительно нужно проверить состояние после исключения, для каждого метода, вы всегда можете вернуться к этой идиоме:

@Test
public void testExpectedException()
{
  MockedObject mockObj = mock(MockedObj.class);
  MySubject subject = new MySubject(mockedObj);
  try {
    subject.someMethodThrowingException();
    fail("Expected MyException.");
  } catch (MyException expected) {
    assertEquals("My exception message.", expected.getMessage());
  }
  verify(mockObj).someCleanup(eq(...));
}

Обновление: с помощью лямбда-выражений Java 8 вы можете заключить вызов функционального интерфейса в блок try достаточно кратко, чтобы быть полезным. Я полагаю, что поддержка этого синтаксиса найдет свое отражение во многих стандартных библиотеках тестирования.

assertThrows(MyException.class,
    () -> systemUnderTest.throwingMethod());

Ответ 2

Более элегантное решение с catch-exception

@Test
public void testExpectedException()
{
    MockedObject mockObj = mock(MockedObject.class);
    MySubject subject = new MySubject(mockObj);

    when(subject).someMethodThrowingException();

    then(caughtException())
            .isInstanceOf(MyException.class)
            .hasMessage("My exception message.");

    verify(mockObj).someCleanup(eq(...));
}

Ответ 3

Я еще не пробовал этого, но в дополнение к отличному ответу Джеффа Боумена у вас может быть выбор использовать правило ExpectedException с попыткой... окончательно построить, поместив ваш оператор проверки в блок finally.

Ответ 4

Как только исключение выдается в UT, весь приведенный ниже код будет игнорироваться.

@Test(expected = Exception.class)
public void testExpectedException() {
   MockedObject mockObj = mock(MockedObj.class);
   MySubject subject = new MySubject(mockedObj);
   subject.doSomething(); // If this line results in an exception then all the code below this will be ignored.
   subject.someMethodThrowingException();
   verify(mockObj).
       someCleanup(eq(...));
}

Чтобы противостоять этому и проверить все сделанные вызовы, мы можем использовать try with finally.

@Test(expected = Exception.class)
    public void testExpectedException() {
          MockedObject mockObj = mock(MockedObj.class);
          MySubject subject = new MySubject(mockedObj);
          try {
               subject.someMethodThrowingException(); 
          } finally {
             verify(mockObj).
             someCleanup(eq(...));
          }
}