Программирование

Как проверять исключения в тестах JUnit: лучшие практики

Идиоматичные способы проверки исключений в JUnit тестах: assertThrows в JUnit 5, ExpectedException в JUnit 4. Примеры кода, сравнение подходов и лучшие практики для junit тестов с проверкой сообщений.

Как проверять исключения в тестах JUnit: Лучшие практики для проверки исключений

Как можно использовать JUnit идиоматически для проверки, что некоторый код выбрасывает исключение?

Хотя я могу, конечно, сделать что-то вроде этого:

java
@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

Твой старый способ с try-catch работает, но в JUnit 4 есть аннотация @Test(expected = ExceptionClass.class), которая делает код чище. Просто укажи ожидаемый тип исключения — и фреймворк сам перехватит его в любом месте метода теста.

Представь метод, который делит на ноль:

java
@Test(expected = ArithmeticException.class)
public void testDivisionByZero() {
 int result = 10 / 0; // Бум! ArithmeticException
}

Если исключение вылетит — тест зеленый. Плюс в простоте, минус — нет контроля над местом выброса или сообщением. Официальная документация JUnit упоминает это как базовый инструмент, но советует для продвинутых случаев переходить дальше. А что если исключение нужно проверить не целиком метод, а конкретный блок? Тут аннотация слепа.


ExpectedException Rule для детальной проверки

В JUnit 4 для точности используй @Rule public ExpectedException thrown = ExpectedException.none();. Это правило позволяет ожидать исключение, проверять тип, сообщение и даже матчеры.

Смотри пример из руководства по JUnit:

java
@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:

java
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:

java
@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 тестов

  1. Не полагайся на @Test(expected) — оно слепо к позиции. Лучше assertThrows.
  2. Всегда проверяй сообщение: assertEquals("expected msg", ex.getMessage()).
  3. Тестируй граничные случаи: null, пустые коллекции, неверные типы.
  4. Используй Hamcrest или AssertJ для сложных матчеров: assertThat(ex, hasMessage(...)).
  5. В junit 5 maven добавь зависимость: <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId></dependency>.
  6. Избегай try-catch в тестах — это антипаттерн.

Почему? Тесты должны быть declarative, а не imperative. В реальном коде, как в руководстве Barancev, комбинируй с @ParameterizedTest для junit исключения в разных сценариях. И да, на 2026 год все на JUnit 5+.


Источники

  1. Как в JUnit проверять ожидаемые исключения
  2. Тестирование исключений в JUnit - Skypro
  3. Тест JUnit на исключение - Stack Overflow RU
  4. Проверка выброса исключения в JUnit 5 - Skypro
  5. How to assert an exception is thrown with JUnit 5? - Stack Overflow
  6. Assert (JUnit API)
  7. Assert an Exception Is Thrown in JUnit 4 and 5 - Baeldung
  8. JUnit Exception Test - CoderLessons

Заключение

Для junit тесты идеал — assertThrows из JUnit 5: просто, точно, гибко. Забудь ручной флаг — используй его с проверкой сообщений, и твои junit исключения станут надежными. Если на JUnit 4, мигрируй на ExpectedException или сразу на 5. В итоге тесты короче, читаемее, и баги ловятся раньше. Попробуй в своем проекте — разница ощутима!

Авторы
Проверено модерацией
Модерация
Как проверять исключения в тестах JUnit: лучшие практики