23

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

21

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

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    boolean thrown = false;

    try {
        foo.doStuff();
    } catch (IndexOutOfBoundsException e) {
        thrown = true;
    }

    assertTrue(thrown);
}

Но я помню, что существует аннотация или метод Assert.xyz или что-то подобное, что будет гораздо более элегантным и соответствующим духу JUnit для таких ситуаций. Какое правильное решение для тестирования выброса исключений в JUnit?

4 ответ(ов)

5

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

Я обычно использую это для тестирования валидации параметров, так как такие методы, как правило, очень просты. Однако для более сложных тестов может быть более уместно воспользоваться следующим подходом:

try {
    methodThatShouldThrow();
    fail("Мой метод не выбросил исключение, как я ожидал");
} catch (MyException expectedException) {
}

Применяйте здравый смысл.

2

Как уже упоминалось ранее, существует множество способов обработки исключений в 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

Раскрытие информации: я являюсь автором блога и проекта.

0

Чтобы решить аналогичную задачу, я создал небольшой проект:
http://code.google.com/p/catch-exception/

С помощью этого небольшого помощника вы можете написать

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Это менее многословно, чем правило ExpectedException в JUnit 4.7. В отличие от решения, предложенного skaffman, вы можете указать, в какой строке кода вы ожидаете исключение. Надеюсь, это будет полезно.

0

Вы можете сделать это следующим образом:

@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());
}

Этот подход более идиоматичен и лучше отражает намерение теста.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь