Avatar ·

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

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

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

Вы столкнулись с классической задачей тестирования. Просто вызвать метод и ожидать исключение — неправильно, потому что JUnit интерпретирует любое неперехваченное исключение как ошибку теста. Вот три основных подхода, от простого к продвинутому.

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

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

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

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

2. Блок try-catch (гибкий, но многословный)

Позволяет проверить детали исключения:

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

Ключевой момент — fail() после вызова метода. Если исключение не выбросится, выполнение дойдёт до 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() {
        Calculator calc = new Calculator();
        thrown.expect(IllegalArgumentException.class);
        thrown.expectMessage("Делитель не может быть нулём");
        calc.divide(10, 0);
    }
}

Правило работает как «ожидатель»: вы настраиваете его перед вызовом метода, и если исключение совпадает — тест зелёный. Можно проверить и тип, и сообщение, и даже причину (expectCause()). Важно: вызов метода должен быть последним — если после него будет ещё код, он не выполнится.

Какой способ выбрать?

  • @Test(expected = ...) — для быстрых проверок, когда важна только сигнатура исключения.
  • try-catch + fail() — когда нужно проверить сообщение, cause или выполнить действия после исключения (например, убедиться, что объект остался в консистентном состоянии).
  • ExpectedException — лучший выбор для 80% случаев: читаемо, гибко, не требует лишних конструкций.

В JUnit 5 (Jupiter) подход изменился — появился assertThrows(), который ещё удобнее. Но для JUnit 4 описанные методы остаются стандартом.

<<>>

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

Если вы планируете переходить на JUnit 5 (Jupiter), задача решается гораздо элегантнее. В JUnit 5 не нужно правил или атрибутов — есть статический метод assertThrows():

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

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

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

  • Лямбда-выражение делает код чистым и понятным.
  • Метод возвращает объект исключения — можно сразу проверить его свойства.
  • Не нужно использовать @Rule или fail() — всё в одном вызове.

Если вы пишете новый проект, рекомендую сразу использовать JUnit 5. Он обратно совместим с JUnit 4 через механизм Vintage, но для новых тестов лучше использовать родной API.