Как вы утверждаете, что в тестах JUnit 4 выбрасывается определенное исключение? - PullRequest
1834 голосов
/ 01 октября 2008

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

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

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

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

  assertTrue(thrown);
}

Я вспоминаю, что есть аннотация или Assert.xyz или что-то , что гораздо менее хитро и гораздо более в духе JUnit для подобных ситуаций.

Ответы [ 33 ]

2204 голосов
/ 01 октября 2008

JUnit 4 поддерживает это:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Ссылка: https://junit.org/junit4/faq.html#atests_7

1251 голосов
/ 29 мая 2010

Редактировать Теперь, когда JUnit5 выпущен, лучшим вариантом будет использовать Assertions.assertThrows() (см. мой другой ответ ).

Если вы не мигрировали в JUnit 5, но можете использовать JUnit 4.7, вы можете использовать правило ExpectedException:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Это намного лучше, чем @Test(expected=IndexOutOfBoundsException.class), потому что тест не пройдёт, если IndexOutOfBoundsException будет брошен до foo.doStuff()

Подробнее см. в этой статье

446 голосов
/ 01 октября 2008

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

Я склонен использовать это для проверки правильности параметров, потому что такие методы обычно очень просты, но более сложные тесты лучше использовать с:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Применить решение.

203 голосов
/ 08 июля 2014

Как ответили ранее, в JUnit есть много способов справиться с исключениями. Но с Java 8 есть еще один: использование лямбда-выражений. С помощью лямбда-выражений мы можем получить такой синтаксис:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .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

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

109 голосов
/ 05 августа 2015

в junit, есть четыре способа проверить исключение.

  • для junit4.x, используйте необязательный атрибут «ожидаемый» аннотации теста

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • для junit4.x, используйте правило ExpectedException

    public class XxxTest {
        @Rule
        public ExpectedException thrown = ExpectedException.none();
    
        @Test
        public void testFooThrowsIndexOutOfBoundsException() {
            thrown.expect(IndexOutOfBoundsException.class)
            //you can test the exception message like
            thrown.expectMessage("expected messages");
            foo.doStuff();
        }
    }
    
  • вы также можете использовать классический способ try / catch, широко используемый в рамках инфраструктуры junit 3

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        try {
            foo.doStuff();
            fail("expected exception was not occured.");
        } catch(IndexOutOfBoundsException e) {
            //if execution reaches here, 
            //it indicates this exception was occured.
            //so we need not handle it.
        }
    }
    
  • наконец, для junit5.x вы также можете использовать assertThrows следующим образом

    @Test
    public void testFooThrowsIndexOutOfBoundsException() {
        Throwable exception = assertThrows(IndexOutOfBoundsException.class, () -> foo.doStuff());
        assertEquals("expected messages", exception.getMessage());
    }
    
  • так

    • 1-й способ используется, когда вы хотите проверить только тип исключения
    • другие три способа используются, когда вы хотите дополнительно проверить сообщение об исключении
    • если вы используете junit 3, то предпочтительнее использовать третий
    • если вам нравится junit 5, то вам должен понравиться 4-й
  • для получения дополнительной информации, вы можете прочитать этот документ и junit5 руководство пользователя для подробностей.

95 голосов
/ 07 декабря 2016

ТЛ; др

  • pre-JDK8: я буду рекомендовать старый добрый блок try - catch. ( Не забудьте добавить fail() утверждение перед catch блоком )

  • post-JDK8: используйте AssertJ или пользовательские лямбды, чтобы утверждать исключительное поведение.

Независимо от Junit 4 или JUnit 5.

длинная история

Можно написать себе сделать это самостоятельно try - catch блок или использовать инструменты JUnit (@Test(expected = ...) или @Rule ExpectedException функция правила JUnit).

Но эти способы не так элегантны и плохо сочетаются с точки зрения читаемости с другими инструментами. Кроме того, у инструментов JUnit есть некоторые подводные камни.

  1. В блоке try - catch вы должны написать блок вокруг проверенного поведения и записать утверждение в блоке catch, что может быть хорошо, но многие находят, что этот стиль прерывает поток чтения тест. Также вам нужно написать Assert.fail в конце блока try, иначе тест может пропустить одну сторону утверждений; PMD , findbugs или Sonar определит такие проблемы.

  2. Функция @Test(expected = ...) интересна тем, что вы можете написать меньше кода, и тогда написание этого теста предположительно менее подвержено ошибкам кодирования. Но у этого подхода не хватает некоторых областей.

    • Если тест должен проверить дополнительные вещи в исключении, такие как причина или сообщение (хорошие сообщения об исключениях действительно важны, точного типа исключения может быть недостаточно).
    • Кроме того, поскольку в методе заложено ожидание, в зависимости от того, как написан проверенный код, неправильная часть тестового кода может вызвать исключение, что приведет к ложному положительному тесту, и я не уверен, что PMD , findbugs или Sonar даст подсказки по такому коду.

      @Test(expected = WantedException.class)
      public void call2_should_throw_a_WantedException__not_call1() {
          // init tested
          tested.call1(); // may throw a WantedException
      
          // call to be actually tested
          tested.call2(); // the call that is supposed to raise an exception
      }
      
  3. Правило ExpectedException также является попыткой исправить предыдущие предупреждения, но его использование немного неудобно, поскольку используется стиль ожидания, EasyMock пользователи очень хорошо знают этот стиль. Для некоторых это может быть удобно, но если вы будете следовать Behavior-Driven Development (BDD) или Arrange Act Assert (AAA), правило ExpectedException не будет соответствовать этому стилю написания , Помимо этого он может страдать от той же проблемы, что и способ @Test, в зависимости от того, где вы разместили ожидание.

    @Rule ExpectedException thrown = ExpectedException.none()
    
    @Test
    public void call2_should_throw_a_WantedException__not_call1() {
        // expectations
        thrown.expect(WantedException.class);
        thrown.expectMessage("boom");
    
        // init tested
        tested.call1(); // may throw a WantedException
    
        // call to be actually tested
        tested.call2(); // the call that is supposed to raise an exception
    }
    

    Даже ожидаемое исключение помещается перед оператором теста, оно нарушает ваш поток чтения, если тесты следуют BDD или AAA.

    Также смотрите этот комментарий вопрос о JUnit автора ExpectedException. JUnit 4.13-beta-2 даже не поддерживает этот механизм:

    Запрос на извлечение # 1519 : исключить ожидаемое исключение

    Метод Assert.assertThrows предоставляет более удобный способ проверки исключений. Кроме того, использование ExpectedException подвержено ошибкам при использовании с другими правилами, такими как TestWatcher, поскольку в этом случае важен порядок правил.

Таким образом, все вышеперечисленные опции имеют всю свою предосторожность и явно не защищены от ошибок кодера.

  1. Есть проект, о котором я узнал после создания этого ответа, который выглядит многообещающим, это catch-исключение .

    Как говорится в описании проекта, он позволяет кодировщику писать в беглой строке кода, перехватывающей исключение, и предлагает это исключение для последующего утверждения. И вы можете использовать любую библиотеку утверждений, такую ​​как Hamcrest или AssertJ .

    Быстрый пример взят с домашней страницы:

    // given: an empty list
    List myList = new ArrayList();
    
    // when: we try to get the first element of the list
    when(myList).get(1);
    
    // then: we expect an IndexOutOfBoundsException
    then(caughtException())
            .isInstanceOf(IndexOutOfBoundsException.class)
            .hasMessage("Index: 1, Size: 0") 
            .hasNoCause();
    

    Как вы видите, код действительно прост, вы ловите исключение в определенной строке, API then - это псевдоним, который будет использовать API AssertJ (аналогично assertThat(ex).hasNoCause()...). В какой-то момент проект опирался на FEST-Assert, предка AssertJ . РЕДАКТИРОВАТЬ: Кажется, что проект назревает поддержка Java 8 Lambdas.

    В настоящее время эта библиотека имеет два недостатка:

    • На момент написания этой статьи стоит отметить, что эта библиотека основана на Mockito 1.x, поскольку она создает макет тестируемого объекта за сценой. Поскольку Mockito все еще не обновлен , эта библиотека не может работать с окончательными классами или конечными методами . И даже если бы он был основан на mockito 2 в текущей версии, для этого потребовалось бы объявить глобального создателя макетов (inline-mock-maker), что может не соответствовать вашему желанию, так как этот mockmaker имеет другие недостатки, чем обычный mockmaker.

    • Требуется еще одна тестовая зависимость.

    Эти проблемы не будут применяться, если библиотека будет поддерживать лямбды, однако функциональность будет дублироваться набором инструментов AssertJ.

    Принимая все во внимание, если вы не хотите использовать инструмент catch-exception, я порекомендую старый добрый способ для блока try - catch, по крайней мере, до JDK7. А для пользователей JDK 8 вы можете предпочесть использовать AssertJ, поскольку он предлагает больше, чем просто утверждение исключений.

  2. С JDK8 лямбды выходят на тестовую сцену, и они доказали, что они представляют собой интересный способ заявить об исключительном поведении. AssertJ был обновлен, чтобы обеспечить хороший свободный API для утверждения исключительного поведения.

    И пример теста с AssertJ :

    @Test
    public void test_exception_approach_1() {
        ...
        assertThatExceptionOfType(IOException.class)
                .isThrownBy(() -> someBadIOOperation())
                .withMessage("boom!"); 
    }
    
    @Test
    public void test_exception_approach_2() {
        ...
        assertThatThrownBy(() -> someBadIOOperation())
                .isInstanceOf(Exception.class)
                .hasMessageContaining("boom");
    }
    
    @Test
    public void test_exception_approach_3() {
        ...
        // when
        Throwable thrown = catchThrowable(() -> someBadIOOperation());
    
        // then
        assertThat(thrown).isInstanceOf(Exception.class)
                          .hasMessageContaining("boom");
    }
    
  3. С почти полным переписыванием JUnit 5 утверждения были улучшены , они могут оказаться интересными как готовый способ утверждать правильно исключение. Но на самом деле API утверждений все еще немного плох, за пределами assertThrows ничего нет.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    Как вы заметили, assertEquals все еще возвращает void, и поэтому не позволяет объединять утверждения типа AssertJ.

    Также, если вы помните столкновение имен с Matcher или Assert, будьте готовы встретить такое же столкновение с Assertions.

Я хотел бы сделать вывод, что сегодня (2017-03-03) AssertJ простота использования, обнаруживаемый API, быстрый темп разработки и как де-факто тест Зависимость - лучшее решение с JDK8 независимо от тестовой среды (JUnit или нет), вместо этого предыдущие JDK должны полагаться на блоки try - catch, даже если они кажутся неуклюжими.

Этот ответ был скопирован с другого вопроса , который не имеет такой же видимости, я тот же автор.

50 голосов
/ 01 октября 2017

Теперь, когда выпущен JUnit 5, лучше всего использовать Assertions.assertThrows() (см. Руководство пользователя Junit 5 ).

Вот пример, который проверяет, что исключение выдается, и использует Truth , чтобы сделать утверждения в сообщении об исключении:

public class FooTest {
  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

Преимущества над подходами в других ответах:

  1. Встроенный в JUnit
  2. Вы получаете полезное сообщение об исключении, если код в лямбде не генерирует исключение, и трассировку стека, если оно вызывает другое исключение
  3. Сжатый
  4. Позволяет вашим тестам следовать Arrange-Act-Assert
  5. Вы можете точно указать, какой код вы ожидаете выбросить исключение
  6. Вам не нужно указывать ожидаемое исключение в предложении throws
  7. Вы можете использовать выбранную платформу утверждений, чтобы делать утверждения о перехваченном исключении

Аналогичный метод будет добавлен к org.junit Assert в JUnit 4.13.

40 голосов
/ 01 октября 2008

Как насчет этого: поймайте очень общее исключение, убедитесь, что оно выбрано из блока catch, а затем подтвердите, что класс исключения соответствует ожидаемому. Это утверждение не будет выполнено, если а) исключение будет неправильного типа (например, если вместо этого вы получили нулевой указатель) и б) исключение не было выдано.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

  try {
    foo.doStuff();
  } catch (Throwable ex) {
    e = ex;
  }

  assertTrue(e instanceof IndexOutOfBoundsException);
}
34 голосов
/ 15 ноября 2013

BDD Стиль Решение: JUnit 4 + Исключение улова + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

    then(caughtException()).isInstanceOf(IndexOutOfBoundsException.class);

}

Исходный код

1019 * Зависимость * eu.codearte.catch-exception:catch-exception:1.3.3

33 голосов
/ 05 марта 2016

Используя утверждение AssertJ , которое можно использовать вместе с JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

Это лучше, чем @Test(expected=IndexOutOfBoundsException.class), потому что он гарантирует, что ожидаемая строка в тесте вызвала исключение и позволяет вам проверить более подробную информацию об исключении, например, сообщение:

assertThatThrownBy(() ->
       {
         throw new Exception("boom!");
       })
    .isInstanceOf(Exception.class)
    .hasMessageContaining("boom");

Инструкции Maven / Gradle здесь.

...