Avatar ·

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

Всем привет! Я только начал изучать Java и пишу юнит-тесты с помощью JUnit 4. У меня есть метод, который при некорректных входных данных должен выбрасывать IllegalArgumentException. Например, такой метод: ```java public int divide(int a, int b) { if (b == 0) { throw new IllegalArgumentException("Делитель не может быть нулём"); } return a / b; } ``` Я хочу написать тест, который проверит, что при передаче b = 0 действительно выбрасывается это исключение. Пробовал сделать так: ```java @Test public void testDivideByZero() { Calculator calc = new Calculator(); calc.divide(10, 0); // ожидаю исключение, но тест падает с ошибкой } ``` Но тест просто падает с исключением, а JUnit считает это ошибкой теста. Я слышал, что есть какой-то `expected` параметр в аннотации `@Test` и ещё какие-то правила. Подскажите, как правильно написать такой тест? Важно, чтобы тест проходил зелёным, когда исключение выбрасывается, и падал, если исключения нет. И ещё: как проверить не только тип исключения, но и текст сообщения? Буду благодарен за примеры кода. <<>>

Основные способы проверки исключений в JUnit 4

Проблема, с которой вы столкнулись, классическая для начинающих. Когда исключение выбрасывается внутри теста, JUnit по умолчанию считает это ошибкой. Чтобы сказать JUnit: "Нет, это ожидаемое поведение", нужно использовать специальные механизмы. Рассмотрим три основных подхода.

1. Атрибут expected в @Test (самый простой)

Самый лаконичный способ — указать ожидаемое исключение прямо в аннотации:

@Test(expected = IllegalArgumentException.class)
public void testDivideByZero() {
    Calculator calc = new Calculator();
    calc.divide(10, 0);
}

Если метод выбросит IllegalArgumentException, тест пройдёт. Если выбросит другое исключение (например, ArithmeticException) или не выбросит ничего — тест упадёт. Минус: нельзя проверить сообщение исключения.

2. Блок try-catch с assert (гибкий, но громоздкий)

Классический способ из JUnit 3, который работает и в JUnit 4:

@Test
public void testDivideByZero() {
    Calculator calc = new Calculator();
    try {
        calc.divide(10, 0);
        fail("Ожидалось IllegalArgumentException");
    } catch (IllegalArgumentException e) {
        assertEquals("Делитель не может быть нулём", e.getMessage());
    }
}

Здесь мы явно ловим исключение и проверяем его сообщение. Если метод не выбросил исключение, выполняется fail() — тест падает. Если выбросил не тот тип — исключение улетит выше и тест тоже упадёт.

3. Правило ExpectedException (JUnit 4.7+)

Более элегантный способ с правилами:

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class CalculatorTest {
    
    @Rule
    public ExpectedException thrown = ExpectedException.none();
    
    @Test
    public void testDivideByZero() {
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("Делитель не может быть нулём");
        
        Calculator calc = new Calculator();
        calc.divide(10, 0);
    }
}

Правило ExpectedException позволяет проверить и тип, и сообщение. Важно: вызов expect() должен быть до вызова тестируемого метода. Можно проверять также причину (expectCause()) и использовать матчеры Hamcrest для более сложных проверок сообщения (например, expectMessage(startsWith("Делитель"))).

Рекомендации

  • Для простой проверки типа исключения используйте @Test(expected=...) — это минималистично и читаемо.
  • Если нужно проверить сообщение или другие детали, используйте ExpectedException (или assertThrows из JUnit 5, если перейдёте на новую версию).
  • Избегайте пустых catch-блоков — это скрывает ошибки.
  • Помните: тест должен проверять только одно исключение. Если в методе несколько точек выброса, пишите отдельные тесты для каждой.

В JUnit 5 (Jupiter) появился метод assertThrows, который делает то же самое ещё проще:

import static org.junit.jupiter.api.Assertions.assertThrows;

@Test
void testDivideByZero() {
    Calculator calc = new Calculator();
    IllegalArgumentException exception = assertThrows(
        IllegalArgumentException.class,
        () -> calc.divide(10, 0)
    );
    assertEquals("Делитель не может быть нулём", exception.getMessage());
}

Но это уже для JUnit 5, а вы, судя по вопросу, используете JUnit 4.

<<>>

Альтернативный подход: JUnit 5 (Jupiter) с assertThrows

Если у вас есть возможность перейти на JUnit 5, я настоятельно рекомендую использовать встроенный метод assertThrows. Он решает все проблемы JUnit 4 в одном флаконе:

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;

@Test
void testDivideByZero() {
    Calculator calc = new Calculator();
    
    // Проверяем, что исключение выбрасывается, и получаем его
    IllegalArgumentException exception = assertThrows(
        IllegalArgumentException.class,
        () -> calc.divide(10, 0)
    );
    
    // Дополнительно проверяем сообщение
    assertEquals("Делитель не может быть нулём", exception.getMessage());
}

Преимущества:

  • Не нужно использовать @Rule или блоки try-catch.
  • Лямбда-выражение делает код компактным.
  • Можно проверить любое количество свойств исключения.
  • Метод возвращает сам объект исключения для дальнейших проверок.

Если вы пишете новый проект или можете обновить зависимости, мигрируйте на JUnit 5. Это упростит тестирование не только исключений, но и многих других сценариев. В JUnit 4 же лучшим выбором остаётся ExpectedException или @Test(expected=...) для простых случаев.