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


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

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

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

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

  assertTrue(thrown);
}

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


1586


источник


Ответы:


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


1892



редактировать Теперь, когда 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()

Видеть Эта статья для подробностей


1151



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

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

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

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


400



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

@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

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


186



в 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, то предпочтительнее 3-й
    • если вам нравится junit 5, то вам понравится 4-й
  • для получения дополнительной информации вы можете прочитать этот документ а также junit5 руководство пользователя для деталей.


77



ТЛ; др

  • pre-JDK8: я порекомендую старый добрый try- catchблок.

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

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

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

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

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

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

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

      @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) или Упорядочить действие (ААА) 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,

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

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

    • Это требует еще одной тестовой зависимости.

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

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

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


49



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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

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

}

Исходный код

зависимости

eu.codearte.catch-exception:catch-exception:1.3.3

31



How about this: Catch a very general exception, make sure it makes it out of the catch block, then assert that the class of the exception is what you expect it to be. This assert will fail if a) the exception is of the wrong type (eg. if you got a Null Pointer instead) and b) the exception wasn't ever thrown.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

30



Using an AssertJ assertion, which can be used alongside JUnit:

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

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

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

It's better than @Test(expected=IndexOutOfBoundsException.class) because it guarantees the expected line in the test threw the exception and lets you check more details about the exception, such as message, easier:

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

Maven/Gradle instructions here.


30



To solve the same problem I did set up a small project: http://code.google.com/p/catch-exception/

Using this little helper you would write

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

This is less verbose than the ExpectedException rule of JUnit 4.7. In comparison to the solution provided by skaffman, you can specify in which line of code you expect the exception. I hope this helps.


29



Now that JUnit 5 has released, the best option is to use Assertions.assertThrows() (see the Junit 5 User Guide).

Here is an example that verifies an exception is thrown, and uses Truth to make assertions on the exception message:

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

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

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

The advantages over the approaches in the other answers are:

  1. Built into JUnit
  2. You get a useful exception message if the code in the lambda doesn't throw an exception, and a stacktrace if it throws a different exception
  3. Concise
  4. Allows your tests to follow Arrange-Act-Assert
  5. You can precisely indicate what code you are expecting to throw the exception
  6. You don't need to list the expected exception in the throws clause
  7. You can use the assertion framework of your choice to make assertions about the caught exception

A similar method will be added to org.junit Assert in JUnit 4.13.


25



Update: JUnit5 has an improvement for exceptions testing: assertThrows.

following example is from: Junit 5 User Guide

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

Original answer using JUnit 4.

There are several ways to test that an exception is thrown. I have also discussed the below options in my post How to write great unit tests with JUnit

Set the expected parameter @Test(expected = FileNotFoundException.class).

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

Using 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!"));
    }

}

Testing with ExpectedException Rule.

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

You could read more about exceptions testing in JUnit4 wiki for Exception testing and bad.robot - Expecting Exceptions JUnit Rule.


24