Як я можу стверджувати своє повідомлення про виключення з анотацією тесту 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.