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

2001

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

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

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

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

  assertTrue(thrown);
}

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

SCdF
источник
21
Проблема с любым другим подходом, но это то, что они неизменно заканчивают тест, как только было сгенерировано исключение. Я, с другой стороны, часто все еще хочу вызывать org.mockito.Mockito.verifyс различными параметрами, чтобы убедиться, что определенные вещи произошли (например, служба логгера была вызвана с правильными параметрами) до того, как возникло исключение.
ZeroOne
5
Вы можете посмотреть, как тестировать исключения, на вики-странице JUnit github.com/junit-team/junit/wiki/Exception-testing
PhoneixS
6
@ZeroOne - Для этого у меня было бы два разных теста - один для исключения и один для проверки взаимодействия с вашим макетом.
tddmonkey
Есть способ сделать это с помощью JUnit 5, я обновил свой ответ ниже.
Дилини Раджапакша

Ответы:

2363

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

Оригинальный ответ JUnit <= 4.12был:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {

    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);

}

Хотя ответ https://stackoverflow.com/a/31826781/2986984 имеет больше возможностей для JUnit <= 4.12.

Ссылка :

skaffman
источник
66
Этот фрагмент кода не будет работать, если вы ожидаете исключение только где-то в вашем коде, а не такое одеяло, как этот.
О, Чин Бун,
4
@skaffman Это не сработает с org.junit.experimental.theories.Theory, управляемой org.junit.experimental.theories.Theories
Артем Оботуров,
74
Рой Ошеров не одобряет такого рода тестирование исключений в Искусстве модульного тестирования , поскольку исключение может находиться где угодно внутри теста, а не только внутри тестируемого блока.
Кевин Виттек
21
Я не согласен с @ Kiview / Рой Ошеров. На мой взгляд, тесты должны быть для поведения, а не для реализации. Проверяя, что конкретный метод может вызвать ошибку, вы привязываете свои тесты непосредственно к реализации. Я бы сказал, что тестирование по методу, показанному выше, обеспечивает более ценный тест. Предостережение, которое я хотел бы добавить, заключается в том, что в этом случае я бы протестировал пользовательское исключение, чтобы я знал, что получаю исключение, которое действительно хочу.
nickbdyer
6
Ни. Я хочу проверить поведение класса. Что важно, так это то, что если я пытаюсь получить что-то, чего нет, я получаю исключение. Тот факт, что структура данных ArrayListотвечает, не get()имеет значения. Если бы я решил в будущем перейти к примитивному массиву, мне пришлось бы изменить эту тестовую реализацию. Структура данных должна быть скрыта, чтобы тест мог сосредоточиться на поведении класса .
nickbdyer
1317

Изменить: Теперь, когда JUnit 5 и JUnit 4.13 были выпущены, лучший вариант будет использовать Assertions.assertThrows() (для JUnit 5) и Assert.assertThrows()(для JUnit 4.13). Смотрите мой другой ответ для деталей.

Если вы не мигрировали в 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()

Смотрите эту статью для деталей

NamshubWriter
источник
14
@skaffman - Если я правильно понял, похоже, что исключение.expect применяется только в рамках одного теста, а не всего класса.
Бакар
5
Если исключение, которое мы ожидаем получить, является проверенным исключением, следует ли добавить броски или try-catch или протестировать эту ситуацию другим способом?
Мухаммед Джафар Машхади
5
@MartinTrummer Никакой код не должен запускаться после foo.doStuff (), поскольку генерируется исключение и завершается метод. Наличие кода после ожидаемого исключения (за исключением закрытия ресурсов в finally) в любом случае бесполезно, так как его никогда не следует выполнять, если выбрасывается исключение.
Джейсон Томпсон
9
Это лучший подход. Здесь есть два преимущества по сравнению с решением Скаффмана. Во-первых, у ExpectedExceptionкласса есть способы сопоставления сообщения об исключении или даже написания собственного сопоставителя, который зависит от класса исключения. Во-вторых, вы можете установить свое ожидание непосредственно перед строкой кода, в которой вы ожидаете выбросить исключение - это означает, что ваш тест не пройдёт, если неправильная строка кода выдаст исключение; в то время как нет способа сделать это с помощью решения Скаффмана.
Дауд ибн Карим
5
@MJafarMash, если проверяется исключение, которое вы ожидаете выбросить, то вы бы добавили это исключение в предложение throws метода test. Вы делаете то же самое каждый раз, когда тестируете метод, который объявлен для создания проверенного исключения, даже если исключение не сработало в конкретном тестовом примере.
NamshubWriter
472

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

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

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

Примените суждение.

daveb
источник
95
Может быть, я старая школа, но я все еще предпочитаю это. Это также дает мне возможность протестировать само исключение: иногда у меня возникают исключения с геттерами для определенных значений, или я могу просто искать конкретное значение в сообщении (например, искать «xyz» в сообщении «нераспознанный код« xyz »). «).
Родни Гитцель
3
Я думаю, что подход NamshubWriter дает вам лучшее из обоих миров.
Эдди
4
Используя ExpectedException, вы можете вызывать N exception.expect для каждого метода, чтобы проверить, как это исключение.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664
10
@ user1154664 На самом деле, вы не можете. Используя ExpectedException, вы можете только проверить, что один метод вызывает исключение, потому что, когда этот метод вызывается, тест перестает выполняться, потому что он вызвал ожидаемое исключение!
NamshubWriter
2
Ваше первое предложение просто не соответствует действительности. При использовании ExpectedExceptionобычно нужно установить ожидание непосредственно перед строкой, которую вы ожидаете выбросить. Таким образом, если более ранняя строка выдает исключение, оно не сработает, и тест завершится неудачей.
Дауд ибн Карим
213

Как уже говорилось ранее, в 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

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

Рафал Боровец
источник
2
Мне нравится это решение, но могу ли я загрузить его из репозитория Maven?
Селвин
@Airduster Одна из реализаций этой идеи, доступная на Maven, - stefanbirkner.github.io/vallado
NamshubWriter
6
@CristianoFontes более простая версия этого API намечена для JUnit 4.13. См github.com/junit-team/junit/commit/...
NamshubWriter
Технически @RafalBorowiec new DummyService()::someMethodесть MethodHandle, но этот подход одинаково хорошо работает с лямбда-выражениями.
Энди
@NamshubWriter, похоже, что Junit 4.13 был заброшен в пользу Junit 5: stackoverflow.com/questions/156503/…
Вадим
154

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

junit5.x

  • для junit5.x вы можете использовать assertThrowsследующее

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

junit4.x

  • для 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.
        }
    }
  • так

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

Уолш
источник
6
Для меня это лучший ответ, он очень четко охватывает все пути, спасибо! Лично я продолжаю использовать 3-й вариант даже с Junit4 для удобства чтения, чтобы избежать пустого блока catch, вы также можете поймать Throwable и утверждать тип e
Nicolas Cornette
Можно ли использовать ExpectedException, чтобы ожидать проверенное исключение?
miuser
Все это - накопление трех лучших ответов. ИМО, этот ответ даже не нужно было публиковать, если он не добавляет ничего нового. Просто отвечая (популярный вопрос) для респ. Довольно бесполезно.
Пол Самсота
конечно, потому что вы можете передать любой тип, полученный из Trowableметода ExpectedException.expect. посмотри пожалуйста это подпись . @miuser
Уолш
116

ТЛ; др

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

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

Независимо от 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 очень хорошо знают этот стиль. Для некоторых это может быть удобно, но если вы следуете принципам Поведенческого развития (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 : устареть ExpectedException

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

Таким образом, эти вышеупомянутые опции имеют всю их нагрузку и явно не защищены от ошибок кодера.

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

    Как говорится в описании проекта, он позволяет кодировщику писать в беглой строке кода, перехватывающей исключение, и предлагает это исключение для последнего утверждения. И вы можете использовать любую библиотеку утверждений, такую ​​как 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();

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

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

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

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

    Эти проблемы не будут применяться, если библиотека поддерживает лямбды. Однако функциональность будет дублироваться набором инструментов 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 или нет) ранее JDKs вместо этого следует полагаться на try-catch блоков , даже если они чувствуют себя неуклюжими.

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

Brice
источник
1
Добавление org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 (в дополнение к уже существующему junit: junit: 4.12) для возможности использования assertThrows, возможно, не является предпочтительным решением, но не вызывает каких-либо вопросы для меня.
17
Я фанат использования правила ExpectedException, но меня всегда беспокоило, что оно нарушает AAA. Вы написали отличную статью, чтобы описать все различные подходы, и вы определенно призвали меня попробовать AssertJ :-) Спасибо!
Пим Хазебрук
@PimHazebroek спасибо. AssertJ API довольно богат. На мой взгляд, лучше то, что предлагает JUnit из коробки.
Брайс
64

Теперь, когда JUnit 5 и JUnit 4.13 были выпущены, лучшим вариантом будет использование Assertions.assertThrows() (для JUnit 5) и Assert.assertThrows()(для JUnit 4.13). Смотрите руководство пользователя 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. Встроенный в Юнит
  2. Вы получите полезное сообщение об исключении, если код в лямбде не выдает исключение, и трассировку стека, если оно выдает другое исключение
  3. краткий
  4. Позволяет вашим тестам следовать Arrange-Act-Assert
  5. Вы можете точно указать, какой код вы ожидаете выбросить исключение
  6. Вам не нужно указывать ожидаемое исключение в throws предложении
  7. Вы можете использовать выбранную платформу утверждений, чтобы делать утверждения о перехваченном исключении

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

NamshubWriter
источник
Этот подход является чистым, но я не вижу, как это позволяет нашему тесту следовать «Arrange-Act-Assert», так как мы должны заключить часть «Act» в «assertThrow», который является assert.
Заводной
@ Clockwork Лямбда - это «акт». Цель Arrange-Act-Assert - сделать код понятным и простым (и, следовательно, легким для понимания и сопровождения). Как вы заявили, этот подход является чистым.
NamshubWriter
Я все еще надеялся, что смогу утверждать бросок и исключение в конце теста, хотя и в части «assert». При таком подходе вам нужно обернуть акт первым утверждением, чтобы поймать его первым.
Заводной
Это потребует больше кода в каждом тесте, чтобы сделать утверждение. Это больше кода и будет подвержен ошибкам.
NamshubWriter
43

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

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}
Johan
источник
3
Кроме того, вы не увидите, что такое Exception ex в результатах теста, когда придет день, когда тест не пройден.
Jontejj
Это можно немного улучшить, изменив то, как вы утверждаете в конце. assertEquals(ExpectedException.class, e.getClass())покажет вам ожидаемые и фактические значения, когда тест не пройден.
Cypher
37

Решение в стиле BDD : JUnit 4 + Catch Exception + AssertJ

import static com.googlecode.catchexception.apis.BDDCatchException.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(() -> foo.doStuff());

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

}

зависимости

eu.codearte.catch-exception:catch-exception:2.0
MariuszS
источник
36

Используя утверждение 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 здесь.

Уэстон
источник
самый краткий способ, и никто не оценит это, странно .. У меня есть только одна проблема с библиотекой assertJ, assertThat по названию конфликтует с junit. больше о assertJ throwby: JUnit: тестирование исключений с Java 8 и AssertJ 3.0.0 ~ Codeleak.pl
ycomp
@ycomp Ну, это новый ответ на очень старый вопрос, поэтому разница в баллах обманчива.
Уэстон
Это, вероятно, лучшее решение, если можно использовать Java 8 и AssertJ!
Пьер Генри
@ycomp Я подозреваю, что этот конфликт имен может быть умышленным: поэтому библиотека AssertJ настоятельно рекомендует вам никогда не использовать JUnit assertThat, всегда AssertJ. Кроме того, метод JUnit возвращает только «обычный» тип, тогда как метод AssertJ возвращает AbstractAssertподкласс ..., позволяющий структурировать методы, как указано выше (или любые другие технические термины для этого ...).
Майк Грызун
@ Weston на самом деле я только что использовал вашу технику в AssertJ 2.0.0. Без сомнения, нет оправдания для обновления, но, возможно, вы хотели бы знать.
Майк Грызун
33

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

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

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

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

rwitzel
источник
Я подумал о том, чтобы сделать что-то подобное, но в конечном итоге обнаружил, что истинная сила ExpectedException заключается в том, что вы можете не только указать ожидаемое исключение, но вы также можете указать некоторые свойства исключения, такие как ожидаемая причина или ожидаемое сообщение.
Джейсон Томпсон
Я думаю, что это решение имеет те же недостатки, что и макеты? Например, если fooэто finalбудет не потому , что вы не можете прокси foo?
Том
Том, если doStuff () является частью интерфейса, прокси-подход будет работать. В противном случае этот подход потерпит неудачу, вы правы.
rwitzel
31

Обновление: JUnit5 имеет улучшение для тестирования исключений:assertThrows .

Следующий пример взят из: Руководство пользователя Junit 5

 @Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> 
    {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

Оригинальный ответ с использованием JUnit 4.

Есть несколько способов проверить, что выбрасывается исключение. Я также обсудил ниже варианты в моем посте Как написать отличные юнит-тесты с JUnit

Установите expectedпараметр @Test(expected = FileNotFoundException.class).

@Test(expected = FileNotFoundException.class) 
public void testReadFile() { 
    myClass.readFile("test.txt");
}

С помощью try catch

public void testReadFile() { 
    try {
        myClass.readFile("test.txt");
        fail("Expected a FileNotFoundException to be thrown");
    } catch (FileNotFoundException e) {
        assertThat(e.getMessage(), is("The file test.txt does not exist!"));
    }

}

Тестирование с ExpectedExceptionправилом.

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testReadFile() throws FileNotFoundException {

    thrown.expect(FileNotFoundException.class);
    thrown.expectMessage(startsWith("The file test.txt"));
    myClass.readFile("test.txt");
}

Вы можете прочитать больше о тестировании исключений в вики JUnit4 для тестирования исключений и bad.robot - Правило исключения JUnit Expecting .

Дилини Раджапакша
источник
22

Вы также можете сделать это:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}
Джон Микич
источник
12
В тестах JUnit лучше использовать Assert.fail(), а не assertна тот случай, если ваши тесты выполняются в среде, где утверждения не включены.
NamshubWriter
14

ИМХО, лучший способ проверить наличие исключений в JUnit - это шаблон try / catch / fail / assert:

// this try block should be as small as possible,
// as you want to make sure you only catch exceptions from your code
try {
    sut.doThing();
    fail(); // fail if this does not throw any exception
} catch(MyException e) { // only catch the exception you expect,
                         // otherwise you may catch an exception for a dependency unexpectedly
    // a strong assertion on the message, 
    // in case the exception comes from anywhere an unexpected line of code,
    // especially important if your checking IllegalArgumentExceptions
    assertEquals("the message I get", e.getMessage()); 
}

Это assertTrueможет быть немного сильным для некоторых людей, поэтому assertThat(e.getMessage(), containsString("the message");может быть предпочтительнее.

Алекс Коллинз
источник
13

Самый гибкий и элегантный ответ для Junit 4 я нашел в блоге Mkyong . Он имеет гибкость try/catchиспользования @Ruleаннотации. Мне нравится этот подход, потому что вы можете прочитать определенные атрибуты настроенного исключения.

package com.mkyong;

import com.mkyong.examples.CustomerService;
import com.mkyong.examples.exception.NameNotFoundException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasProperty;

public class Exception3Test {

    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void testNameNotFoundException() throws NameNotFoundException {

        //test specific type of exception
        thrown.expect(NameNotFoundException.class);

        //test message
        thrown.expectMessage(is("Name is empty!"));

        //test detail
        thrown.expect(hasProperty("errCode"));  //make sure getters n setters are defined.
        thrown.expect(hasProperty("errCode", is(666)));

        CustomerService cust = new CustomerService();
        cust.findByName("");

    }

}
Dherik
источник
12

Я попробовал многие методы здесь, но они были либо сложными, либо не совсем отвечали моим требованиям. На самом деле, можно написать вспомогательный метод довольно просто:

public class ExceptionAssertions {
    public static void assertException(BlastContainer blastContainer ) {
        boolean caughtException = false;
        try {
            blastContainer.test();
        } catch( Exception e ) {
            caughtException = true;
        }
        if( !caughtException ) {
            throw new AssertionFailedError("exception expected to be thrown, but was not");
        }
    }
    public static interface BlastContainer {
        public void test() throws Exception;
    }
}

Используйте это так:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

Нулевые зависимости: нет необходимости в mockito, нет необходимости в powermock; и отлично работает с выпускными классами.

Хью Перкинс
источник
Интересно, но не подходит для AAA (Arrange Act Assert), где вы хотите выполнить действия Act и Assert фактически разными шагами.
Bln-Tom
1
@ bln-tom Технически это два разных шага, они просто не в таком порядке. ; p
Trejkaz
10

Java 8 решение

Если вы хотите решение, которое:

  • Использует лямбды Java 8
  • Разве не зависит ни от какой JUnit магии
  • Позволяет проверить наличие нескольких исключений в одном методе тестирования.
  • Проверяет исключение, генерируемое определенным набором строк в вашем методе тестирования вместо любой неизвестной строки во всем методе теста
  • Возвращает действительный объект исключения, который был сгенерирован, чтобы вы могли его дополнительно изучить

Вот полезная функция, которую я написал:

public final <T extends Throwable> T expectException( Class<T> exceptionClass, Runnable runnable )
{
    try
    {
        runnable.run();
    }
    catch( Throwable throwable )
    {
        if( throwable instanceof AssertionError && throwable.getCause() != null )
            throwable = throwable.getCause(); //allows "assert x != null : new IllegalArgumentException();"
        assert exceptionClass.isInstance( throwable ) : throwable; //exception of the wrong kind was thrown.
        assert throwable.getClass() == exceptionClass : throwable; //exception thrown was a subclass, but not the exact class, expected.
        @SuppressWarnings( "unchecked" )
        T result = (T)throwable;
        return result;
    }
    assert false; //expected exception was not thrown.
    return null; //to keep the compiler happy.
}

( взято из моего блога )

Используйте это следующим образом:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}
Майк Накис
источник
8

В моем случае я всегда получаю RuntimeException от db, но сообщения различаются. И исключение должно быть обработано соответственно. Вот как я это проверил:

@Test
public void testThrowsExceptionWhenWrongSku() {

    // Given
    String articleSimpleSku = "999-999";
    int amountOfTransactions = 1;
    Exception exception = null;

    // When
    try {
        createNInboundTransactionsForSku(amountOfTransactions, articleSimpleSku);
    } catch (RuntimeException e) {
        exception = e;
    }

    // Then
    shouldValidateThrowsExceptionWithMessage(exception, MESSAGE_NON_EXISTENT_SKU);
}

private void shouldValidateThrowsExceptionWithMessage(final Exception e, final String message) {
    assertNotNull(e);
    assertTrue(e.getMessage().contains(message));
}
Macchiatow
источник
1
перед строкой } catch (, вы должны вставитьfail("no exception thrown");
Даниэль Олдер
6

Просто создайте Matcher, который можно выключать и включать, вот так:

public class ExceptionMatcher extends BaseMatcher<Throwable> {
    private boolean active = true;
    private Class<? extends Throwable> throwable;

    public ExceptionMatcher(Class<? extends Throwable> throwable) {
        this.throwable = throwable;
    }

    public void on() {
        this.active = true;
    }

    public void off() {
        this.active = false;
    }

    @Override
    public boolean matches(Object object) {
        return active && throwable.isAssignableFrom(object.getClass());
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("not the covered exception type");
    }
}

Чтобы использовать это:

public ExpectedException exception = ExpectedException.none();затем добавьте :

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();
Tor P
источник
6

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

@Rule
public ExpectedException exceptions = ExpectedException.none();


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

  1. Тип создаваемого исключения
  2. Сообщение об исключении
  3. Причина исключения


public class MyTest {

    @Rule
    public ExpectedException exceptions = ExpectedException.none();

    ClassUnderTest classUnderTest;

    @Before
    public void setUp() throws Exception {
        classUnderTest = new ClassUnderTest();
    }

    @Test
    public void testAppleisSweetAndRed() throws Exception {

        exceptions.expect(Exception.class);
        exceptions.expectMessage("this is the exception message");
        exceptions.expectCause(Matchers.<Throwable>equalTo(exceptionCause));

        classUnderTest.methodUnderTest("param1", "param2");
    }

}
JoBⅈN
источник
6

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

try{
   methodThatThrowMyException();
   Assert.fail("MyException is not thrown !");
} catch (final Exception exception) {
   // Verify if the thrown exception is instance of MyException, otherwise throws an assert failure
   assertTrue(exception instanceof MyException, "An exception other than MyException is thrown !");
   // In case of verifying the error message
   MyException myException = (MyException) exception;
   assertEquals("EXPECTED ERROR MESSAGE", myException.getMessage());
}
Shessuky
источник
3
Второе catchбудет проглотить трассировку стека, если возникнет какое-то другое исключение, потеряв полезную информацию
NamshubWriter
5

В дополнение к тому, что сказал NamShubWriter , убедитесь, что:

  • Экземпляр ExpectedException является открытым ( связанный вопрос )
  • ExpectedException не создается, например, в методе @Before. Этот пост ясно объясняет все тонкости порядка исполнения JUnit.

Вы не делать этого:

@Rule    
public ExpectedException expectedException;

@Before
public void setup()
{
    expectedException = ExpectedException.none();
}

Наконец, этот блог ясно иллюстрирует, как утверждать, что выбрасывается определенное исключение.

Брюс Уэйн
источник
4

Я рекомендую библиотеку assertj-coreдля обработки исключений в тесте junit

В Java 8, как это:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);
Петр Р
источник
2

Решение Junit4 с Java8 заключается в использовании этой функции:

public Throwable assertThrows(Class<? extends Throwable> expectedException, java.util.concurrent.Callable<?> funky) {
    try {
        funky.call();
    } catch (Throwable e) {
        if (expectedException.isInstance(e)) {
            return e;
        }
        throw new AssertionError(
                String.format("Expected [%s] to be thrown, but was [%s]", expectedException, e));
    }
    throw new AssertionError(
            String.format("Expected [%s] to be thrown, but nothing was thrown.", expectedException));
}

Использование тогда:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

Обратите внимание, что единственным ограничением является использование finalссылки на объект в лямбда-выражении. Это решение позволяет продолжить тестовые утверждения вместо ожидаемых результатов на уровне метода с использованием @Test(expected = IndexOutOfBoundsException.class)решения.

Донателло
источник
1

Взять, к примеру, вы хотите написать Junit для указанного ниже фрагмента кода.

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

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

 @Rule
public ExpectedException exception=ExpectedException.none();

private Demo demo;
@Before
public void setup(){

    demo=new Demo();
}
@Test(expected=ArithmeticException.class)
public void testIfItThrowsAnyException() {

    demo.divideByZeroDemo(5, 0);

}

@Test
public void testExceptionWithMessage(){


    exception.expectMessage("Array is out of bound");
    exception.expect(ArrayIndexOutOfBoundsException.class);
    demo.exceptionWithMessage(new String[]{"This","is","a","demo"});
}
Ширш Синха
источник
1
    @Test(expectedException=IndexOutOfBoundsException.class) 
    public void  testFooThrowsIndexOutOfBoundsException() throws Exception {
         doThrow(IndexOutOfBoundsException.class).when(foo).doStuff();  
         try {
             foo.doStuff(); 
            } catch (IndexOutOfBoundsException e) {
                       assertEquals(IndexOutOfBoundsException .class, ex.getCause().getClass());
                      throw e;

               }

    }

Вот еще один способ проверить, правильно ли выброшено исключение или нет.

MangduYogii
источник
1

JUnit Framework имеет assertThrows()метод:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
Lu55
источник
0

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

private void expectException(Runnable r, Class<?> clazz) { 
    try {
      r.run();
      fail("Expected: " + clazz.getSimpleName() + " but not thrown");
    } catch (Exception e) {
      if (!clazz.isInstance(e)) fail("Expected: " + clazz.getSimpleName() + " but " + e.getClass().getSimpleName() + " found", e);
    }
  }

а затем внутри вашего теста:

expectException(() -> list.sublist(0, 2).get(2), IndexOutOfBoundsException.class);

Льготы:

  • не полагаясь ни на какую библиотеку
  • локализованная проверка - более точная и позволяет при необходимости иметь несколько утверждений в одном тесте
  • легко использовать
fahrenx
источник