Как проверить, что в JUnit-тестах выбрасывается определенное исключение?
Как я могу использовать JUnit для идиоматического тестирования того, что определённый код выбрасывает исключение?
Я, конечно, могу сделать что-то вроде этого:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
boolean thrown = false;
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
thrown = true;
}
assertTrue(thrown);
}
Но я помню, что существует аннотация или метод Assert.xyz
или что-то подобное, что будет гораздо более элегантным и соответствующим духу JUnit для таких ситуаций. Какое правильное решение для тестирования выброса исключений в JUnit?
4 ответ(ов)
Будьте осторожны с использованием ожидаемых исключений, поскольку это лишь утверждает, что метод выбросил данное исключение, а не конкретная строка кода в тесте.
Я обычно использую это для тестирования валидации параметров, так как такие методы, как правило, очень просты. Однако для более сложных тестов может быть более уместно воспользоваться следующим подходом:
try {
methodThatShouldThrow();
fail("Мой метод не выбросил исключение, как я ожидал");
} catch (MyException expectedException) {
}
Применяйте здравый смысл.
Как уже упоминалось ранее, существует множество способов обработки исключений в JUnit. Однако с появлением Java 8 появился еще один способ: использование лямбда-выражений. С помощью лямбда-выражений мы можем достичь синтаксиса, подобного следующему:
@Test
public void verifiesTypeAndMessage() {
assertThrown(new DummyService()::someMethod)
.isInstanceOf(RuntimeException.class)
.hasMessage("Произошло исключение времени выполнения")
.hasMessageStartingWith("Исключение")
.hasMessageEndingWith("времени выполнения")
.hasMessageContaining("исключение")
.hasNoCause();
}
Метод assertThrown
принимает функциональный интерфейс, экземпляры которого могут быть созданы с помощью лямбда-выражений, ссылок на методы или ссылок на конструкторы. assertThrown
, принимая этот интерфейс, ожидает и готов обрабатывать исключение.
Это относительно простой, но мощный прием.
Рекомендую ознакомиться с этой статьей в блоге, описывающей данную технику: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html
Исходный код можно найти здесь: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8
Раскрытие информации: я являюсь автором блога и проекта.
Чтобы решить аналогичную задачу, я создал небольшой проект:
http://code.google.com/p/catch-exception/
С помощью этого небольшого помощника вы можете написать
verifyException(foo, IndexOutOfBoundsException.class).doStuff();
Это менее многословно, чем правило ExpectedException в JUnit 4.7. В отличие от решения, предложенного skaffman, вы можете указать, в какой строке кода вы ожидаете исключение. Надеюсь, это будет полезно.
Вы можете сделать это следующим образом:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
try {
foo.doStuff();
assert false;
} catch (IndexOutOfBoundsException e) {
assert true;
}
}
В этом коде мы тестируем метод doStuff()
класса foo
, который должен выбрасывать исключение IndexOutOfBoundsException
. Внутри блока try
мы вызываем этот метод. Если исключение не выбрасывается и код доходит до строки assert false
, это означает, что тест провалился, так как метод должен был выбросить исключение. Если же исключение выбрасывается, мы переходим в блок catch
, где просто утверждаем, что условие истинно (тест проходит).
Однако, для лучшей читаемости и простоты, вы можете использовать Assert.assertThrows
, что делает код более семантически понятным. Например:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
Assert.assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
}
Этот подход более идиоматичен и лучше отражает намерение теста.
Как протестировать класс с приватными методами, полями или внутренними классами?
Можно ли перехватить несколько исключений Java в одном блоке catch?
Как проверить, что конкретный метод не был вызван, используя Mockito?
Как установить Java 8 на Mac
Что значит 'synchronized'?