Як я можу стверджувати своє повідомлення про виключення з анотацією тесту JUnit?


315

Я написав кілька тестів JUnit з @Testанотацією. Якщо мій метод тестування кидає перевірений виняток, і якщо я хочу стверджувати повідомлення разом з винятком, чи є спосіб зробити це з @Testанотацією JUnit ? AFAIK, JUnit 4.7 не надає цю функцію, але чи надають її майбутні версії? Я знаю, що в .NET ви можете стверджувати повідомлення та клас виключення. Шукаєте подібну функцію в світі Java.

Це те, що я хочу:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}

1
Тепер, коли я подумаю про це трохи більше ... Ви впевнені, що це гарна ідея стверджувати повідомлення? Ваше запитання змусило мене трохи заглибитись у вихідний вихідний код, і, здається, вони могли легко додати цю функцію. Те, що вони цього не зробили , змушує мене думати, що це не може вважатися хорошою практикою. Чому у вашому проекті важливо стверджувати повідомлення?
c_maker

10
Добре запитання. Скажіть, що метод, що містить 15 рядків коду, викидає той самий виняток з двох різних місць. У моїх тестових випадках потрібно стверджувати не лише клас виключення, а й повідомлення в ньому. В ідеальному світі будь-яка ненормальна поведінка повинна мати свій виняток. Якщо це було так, моє питання ніколи не виникатиме, але виробничі програми не мають свого унікального спеціального винятку для кожної ненормальної поведінки.
Cshah

Як бічна примітка - @expectedExceptionMessageв PHPUnit є анотація.
bancer

Відповіді:


535

Ви можете використовувати @Ruleпримітку за допомогою ExpectedExceptionтакого:

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

Зауважте, що приклад у ExpectedExceptionдокументах (на даний момент) помиляється - немає публічного конструктора, тому вам доведеться використовувати ExpectedException.none().


1
Примітка: Для мене, коли значення expectMessageбуло вказано як порожній рядок, порівняння для повідомлення не виконувалось
redDevil

1
Корисно для мене. Дякую. Тестовий метод повинен мати throws RuntimeExceptionпісля додавання коду, який видає виняток. Не
впіймай

5
Я особисто не хотів би використовувати це, оскільки створення полів для невеликого набору методів є поганою практикою. Не критика відповіді, а дизайн JUnit. Гіпотетичне рішення ОП було б набагато краще, якби воно існувало.
Шрідхар Сарнобат

2
@redDevil: ОчікуванийMessage перевіряє, чи є повідомлення про помилку "містить" рядок, вказаний у цій функції (як
підстрочка

3
ОчікуєMessage з рядковим параметром перевіряє String.contains, для точної відповідності повідомлення про виключення використовують відповідник hamcrestfailure.expectMessage(CoreMatchers.equalTo(...))
Sivabalan

42

Мені подобається @Ruleвідповідь. Однак, якщо ви чомусь не хочете користуватися правилами. Є третій варіант.

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }

32

Чи потрібно використовувати @Test(expected=SomeException.class)? Коли ми маємо стверджувати фактичне повідомлення про виняток, це ми робимо.

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}

6
Мені відомо про те, що я написав блок ловлі та використовую утвердження в цьому, але для кращої читабельності коду я хочу робити з анотаціями.
Cshah

Також ви не отримаєте такого приємного повідомлення, як, коли це робите "правильним" способом.
NeplatnyUdaj

15
Проблема з версією спробувати / зловити, тепер, JUnit забезпечує @Test(expected=...)і ExpectedExceptionє те , що я бачив багато разів хто - то забудькуватість поставити виклик fail()на кінці tryблоку . Якщо перевірка коду не потрапила, ваш тест може бути помилковим та завжди пройденим.
Вільям Прайс

Ось чому мені не подобаються всі ці декларативні речі. Це ускладнює доступ до того, що ви хочете.
Шрідхар Сарнобат

30

У JUnit 4.13 ви можете:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

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

  assertEquals("a message", exception.getMessage());
}

Це також працює в JUnit 5, але з різним імпортом:

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

...

Як це рішення. Слід переїхати до 5
січня

Гаааааааа. 4.13 все ще в бета-версії на сьогодні (осінь, 2019)? mvnrepository.com/artifact/junit/junit
granadaCoder

v4.13 вже не знаходиться в бета-стані (випуск у січні 2020 року)
Simon

11

Насправді, найкраще використання - це спробувати. Чому? Тому що ви можете контролювати місце, де ви очікуєте винятку.

Розглянемо цей приклад:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

Що робити, якщо одного дня код буде змінено, і підготовка до тестування буде запускати RuntimeException? У цьому випадку фактичний тест навіть не перевіряється, і навіть якщо він не кидає жодного винятку, тест пройде.

Ось чому набагато краще використовувати try / catch, ніж покладатися на анотацію.


На жаль, і це моя відповідь.
Шрідхар Сарнобат

2
Побоювання щодо змін коду полегшуються тим, що є невеликі тестові випадки, характерні для перестановки. Іноді це неминуче, і нам доводиться покладатися на метод лову / спробу, але якщо це трапляється часто, то, швидше за все, нам потрібно переглянути спосіб написання наших тестових функцій.
luis.espinal

Це проблема з вашим тестом та / або кодом. Ви НЕ очікуєте загального RuntimeException, очікуєте конкретного винятку або, принаймні, конкретного повідомлення.
DennisK

Я використовував RuntimeExceptionяк приклад, замінив цей виняток будь-яким іншим винятком.
Кшиштоф Цисло

8

Райсторм мав хорошу відповідь. Я також не великий фанат Правил. Я роблю щось подібне, за винятком того, що створюю наступний клас корисності для полегшення читабельності та зручності використання, що в першу чергу є одним із великих плюсів анотацій.

Додайте цей клас корисності:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

Тоді для мого одиничного тестування мені потрібно лише цей код:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}

2

Якщо використовується @Rule, набір винятків застосовується до всіх методів тестування в класі Test.


2
Використовуючи відповідь Джессі Меррімана, виняток перевіряється лише у тестових методах, які викликають очікуваніEx.expect () та очікуваніEx.expectMessage (). В інших методах буде використане визначення очакванеEx = ExpectedException.none (), тобто не очікується виняток.
Egl

2

Мені ніколи не подобався спосіб стверджувати винятки з Джунітом. Якщо я використовую "очікуване" в анотації, здається, з моєї точки зору, ми порушуємо "заданий, коли, тоді" шаблон, оскільки "тоді" розміщено у верхній частині тестового визначення.

Крім того, якщо ми використовуємо "@Rule", нам доведеться мати справу з стільки кодовим кодом. Отже, якщо ви можете встановити нові бібліотеки для своїх тестів, я б запропонував ознайомитися з AssertJ (ця бібліотека тепер поставляється разом з SpringBoot)

Потім тест, який не порушує принципи "задано / коли / тоді", і робиться за допомогою AssertJ для перевірки:

1 - Виняток - це те, що ми очікуємо. 2 - Це також очікуване повідомлення

Вигляне так:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}

1

Мені подобається відповідь user64141, але я виявив, що вона може бути більш узагальненою. Ось мій взяття:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

Зауважте, що залишення оператора "fail" у блоці спробу спричиняє вилучення відповідного винятку твердження; використання повернення в заяві про вилов це запобігає.


0

Імпортуйте бібліотеку винятків для вилову та скористайтеся цим. Це набагато чистіше, ніж ExpectedExceptionправило чи а try-catch.

Прикладіть форму своїх документів:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);

-2
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}

Чи може хтось допомогти мені зрозуміти, чому ця відповідь -1
ааша

Питання JunitTestNG
задається,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.