Как проверять исключения в тестах JUnit: лучшие практики
Идиоматичные способы проверки исключений в JUnit тестах: assertThrows в JUnit 5, ExpectedException в JUnit 4. Примеры кода, сравнение подходов и лучшие практики для junit тестов с проверкой сообщений.
Как проверять исключения в тестах JUnit: Лучшие практики для проверки исключений
Как можно использовать JUnit идиоматически для проверки, что некоторый код выбрасывает исключение?
Хотя я могу, конечно, сделать что-то вроде этого:
@Test
public void testFooThrowsIndexOutOfBoundsException() {
boolean thrown = false;
try {
foo.doStuff();
} catch (IndexOutOfBoundsException e) {
thrown = true;
}
assertTrue(thrown);
}
Я помню, что есть аннотация или Assert.xyz или что-то, что гораздо менее громоздкое и гораздо больше в духе JUnit для таких ситуаций.
В тестах JUnit проверка исключений делается идиоматично через метод assertThrows() в JUnit 5 или аннотацию @Test(expected = ...) в JUnit 4, что избавляет от громоздкого try-catch с флагом. Эти подходы позволяют не только убедиться в типе исключения вроде IndexOutOfBoundsException, но и проверить его сообщение или другие свойства. Лучшие практики — использовать assertThrows для гибкости и точности, особенно в junit тесты на современном проекте.
Содержание
- Простая проверка исключений в JUnit 4
- ExpectedException Rule для детальной проверки
- assertThrows в JUnit 5: основной метод
- Проверка сообщения и свойств исключения
- Сравнение подходов JUnit 4 и 5
- Лучшие практики для junit тестов
- Источники
- Заключение
Простая проверка исключений в JUnit 4
Твой старый способ с try-catch работает, но в JUnit 4 есть аннотация @Test(expected = ExceptionClass.class), которая делает код чище. Просто укажи ожидаемый тип исключения — и фреймворк сам перехватит его в любом месте метода теста.
Представь метод, который делит на ноль:
@Test(expected = ArithmeticException.class)
public void testDivisionByZero() {
int result = 10 / 0; // Бум! ArithmeticException
}
Если исключение вылетит — тест зеленый. Плюс в простоте, минус — нет контроля над местом выброса или сообщением. Официальная документация JUnit упоминает это как базовый инструмент, но советует для продвинутых случаев переходить дальше. А что если исключение нужно проверить не целиком метод, а конкретный блок? Тут аннотация слепа.
ExpectedException Rule для детальной проверки
В JUnit 4 для точности используй @Rule public ExpectedException thrown = ExpectedException.none();. Это правило позволяет ожидать исключение, проверять тип, сообщение и даже матчеры.
Смотри пример из руководства по JUnit:
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testIndexOutOfBoundsException() {
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 5, Size: 3"); // Точное сообщение
List<String> list = Arrays.asList("a", "b");
list.get(5); // Выбросит исключение
}
Почему круто? Исключение ловится ровно там, где expect вызван после. Нет ложных срабатываний. Stack Overflow рекомендует именно это вместо аннотации для junit exception.
Но! В новых проектах JUnit 4 устаревает. Переходим к пятерке?
assertThrows в JUnit 5: основной метод
Вот где магия JUnit 5. Метод Assertions.assertThrows(Class<T> expectedType, Executable executable) — золотой стандарт для junit тесты. Передает лямбду или Runnable, ловит исключение и возвращает его для проверок.
Классика из Skypro:
import static org.junit.jupiter.api.Assertions.assertThrows;
@Test
void testFooThrowsIndexOutOfBoundsException() {
IndexOutOfBoundsException exception = assertThrows(
IndexOutOfBoundsException.class,
() -> foo.doStuff() // Твой код здесь
);
assertEquals("Плохой индекс", exception.getMessage());
}
Лаконично? Еще бы. Исключение проверяется именно в лямбде, тест фейлится, если не вылетело или тип не тот. Baeldung хвалит за гибкость — можно цеплять assert на свойства. А в junit 5 это дефолт, без правил.
Проверка сообщения и свойств исключения
Просто тип — слабо. Хороший тест проверяет сообщение, cause или поля. В обоих версиях это просто.
Для JUnit 5:
@Test
void detailedExceptionCheck() {
IllegalArgumentException ex = assertThrows(IllegalArgumentException.class,
() -> service.process(null));
assertThat(ex.getMessage()).contains("null не разрешен");
assertNotNull(ex.getCause()); // Если есть причина
}
В JUnit 4 с ExpectedException то же, но через expectMessage. JUnit API добавила assertThrows даже в 4.13, но для миграции на 5 лучше. Ты спросишь: а если несколько исключений? Используй @Test(expected) только для простоты, иначе rule/assertThrows.
Из CoderLessons — всегда проверяй реальные сценарии, типа валидации ввода.
Сравнение подходов JUnit 4 и 5
| Подход | JUnit 4 | JUnit 5 |
|---|---|---|
| Простой | @Test(expected=...) |
❌ Нет |
| Гибкий | ExpectedException rule |
assertThrows() |
| Место проверки | Весь тест | Только лямбда |
| Возврат исключения | Нет | Да, для assert |
| Актуальность | Устаревает | Стандарт |
Stack Overflow на английском показывает, как легко мигрировать: замени rule на assertThrows. JUnit exception в 5 быстрее и чище. А Skypro по JUnit подтверждает: пятерка выигрывает в читаемости.
Лучшие практики для junit тестов
- Не полагайся на @Test(expected) — оно слепо к позиции. Лучше assertThrows.
- Всегда проверяй сообщение:
assertEquals("expected msg", ex.getMessage()). - Тестируй граничные случаи: null, пустые коллекции, неверные типы.
- Используй Hamcrest или AssertJ для сложных матчеров:
assertThat(ex, hasMessage(...)). - В junit 5 maven добавь зависимость:
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId></dependency>. - Избегай try-catch в тестах — это антипаттерн.
Почему? Тесты должны быть declarative, а не imperative. В реальном коде, как в руководстве Barancev, комбинируй с @ParameterizedTest для junit исключения в разных сценариях. И да, на 2026 год все на JUnit 5+.
Источники
- Как в JUnit проверять ожидаемые исключения
- Тестирование исключений в JUnit - Skypro
- Тест JUnit на исключение - Stack Overflow RU
- Проверка выброса исключения в JUnit 5 - Skypro
- How to assert an exception is thrown with JUnit 5? - Stack Overflow
- Assert (JUnit API)
- Assert an Exception Is Thrown in JUnit 4 and 5 - Baeldung
- JUnit Exception Test - CoderLessons
Заключение
Для junit тесты идеал — assertThrows из JUnit 5: просто, точно, гибко. Забудь ручной флаг — используй его с проверкой сообщений, и твои junit исключения станут надежными. Если на JUnit 4, мигрируй на ExpectedException или сразу на 5. В итоге тесты короче, читаемее, и баги ловятся раньше. Попробуй в своем проекте — разница ощутима!