Як ви стверджуєте, що певний виняток є кинутим у тестах JUnit 4?


1998

Як я можу використовувати JUnit4 ідіоматично, щоб перевірити, що якийсь код кидає виняток?

Хоча я, безумовно, можу зробити щось подібне:

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

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

  assertTrue(thrown);
}

Я пам'ятаю, що для таких ситуацій є анотація або Assert.xyz або щось таке, що набагато менш невміло і набагато більше духу JUnit.


21
Проблема будь-якого іншого підходу, але це те, що вони незмінно закінчують тест після того, як викид буде виключено. Я, з іншого боку, часто все-таки хочу зателефонувати org.mockito.Mockito.verifyз різними параметрами, щоб переконатися, що певні речі траплялися (наприклад, виклик служби реєстратора з правильними параметрами) перед тим, як викид був викинутий.
ZeroOne

5
Ви можете побачити, як перевірити винятки на сторінці вікі JUnit на github.com/junit-team/junit/wiki/Exception-testing
PhoneixS

6
@ZeroOne - для цього я мав би два різні тести - один для виключення і один для перевірки взаємодії з вашим знущанням.
tddmonkey

Є спосіб зробити це з JUnit 5, я оновив свою відповідь нижче.
Діліні Раджапакша

Відповіді:


2360

Це залежить від версії 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.

Довідка:


66
Цей фрагмент коду не буде працювати, якщо ви очікуєте виключення лише десь у своєму коді, а не ковдру, як цей.
О Чін Бун

4
@skaffman Це не працюватиме з org.junit.experimental.theories.Theory, керований org.junit.experimental.theories.Theories
Артем Оботуров

74
Рой Ошерове відлякує подібного випробування на винятки в « Техніці тестування одиниць» , оскільки виняток може бути в будь-якому місці всередині тесту, а не тільки в тестовому апараті.
Кевін Віттек,

21
Я не погоджуюся з @ Kiview / Roy Osherove. На мою думку, тести повинні бути на поведінку, а не на реалізацію. Перевіривши, що певний метод може призвести до помилки, ви прив'язуєте свої тести безпосередньо до реалізації. Я б заперечував, що тестування у наведеному вище методі забезпечує більш цінний тест. Я зауважу, що в цьому випадку я б перевірив на власні винятки, щоб я знав, що отримую виняток, якого я дуже хочу.
nickbdyer

5
Ні. Я хочу перевірити поведінку класу. Важливим є те, що якщо я спробую знайти щось, чого там немає, я отримую виняток. Те, що структура даних ArrayListвідповідає на get()це, не має значення. Якби я в майбутньому вирішив перейти до примітивного масиву, мені довелося б змінити цю тестову реалізацію. Структура даних повинна бути прихованою, щоб тест міг зосередити увагу на поведінці класу .
nickbdyer

1315

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

Дивіться цю статтю для деталей


14
@skaffman - Якщо я правильно це зрозумів, виглядає, що виняток.expect застосовується лише в межах одного тесту, а не для всього класу.
бакар

5
Якщо виняток, який ми очікуємо, що буде кинуто, є перевіреним винятком, чи слід додати кидки чи пробувати або перевірити цю ситуацію іншим способом?
Мохаммед Джафар Машхаді

5
@MartinTrummer Жодний код не повинен запускатися після foo.doStuff (), оскільки виняток викинуто і метод вийшов. Надання коду після очікуваного винятку (за винятком закриття ресурсів нарешті) все одно не є корисним, оскільки його ніколи не слід виконувати, якщо виняток буде кинуто.
Джейсон Томпсон

9
Це найкращий підхід. Тут є дві переваги, порівняно з рішенням скафмана. По-перше, у ExpectedExceptionкласі є способи узгодження повідомлення про виключення або навіть написання власного відповідника, який залежить від класу виключення. По-друге, ви можете встановити своє очікування безпосередньо перед рядком коду, за яким ви очікуєте викинути виняток, - це означає, що ваш тест вийде з ладу, якщо неправильний рядок коду викидає виняток; тоді як з рішенням скафмана немає способу зробити це.
Dawood ibn Kareem

5
@MJafarMash, якщо перевіряється виняток, який ви очікуєте кинути, ви додасте це виняток до пункту про кидки методу тестування. Ви робите те саме, коли ви тестуєте метод, який оголошується для викидання перевіреного винятку, навіть якщо виняток не спрацьовує в конкретному тестовому випадку.
NamshubWriter

471

Будьте обережні, використовуючи очікуване виняток, оскільки це лише стверджує, що метод викинув цей виняток, а не конкретний рядок коду в тесті.

Я схильний використовувати це для перевірки параметрів перевірки, оскільки такі методи, як правило, дуже прості, але більш складні тести, можливо, краще:

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

Застосовуйте судження.


95
Можливо, я стара школа, але я все одно віддаю перевагу цьому. Це також дає мені змогу перевірити сам виняток: іноді у мене є винятки із getters для певних значень, або я можу просто шукати певне значення у повідомленні (наприклад, шукати "xyz" у повідомленні "нерозпізнаний код" xyz " ").
Родні Гітцель

3
Я думаю, що підхід NamshubWriter дає вам найкраще з обох світів.
Едді

4
Використовуючи очікуване Exxception, ви можете зателефонувати за винятком N. foo.doStuff1 (); виключення.експедиція (IndexOutOfBoundsException.class); foo.doStuff2 (); виключення.експедиція (IndexOutOfBoundsException.class); foo.doStuff3 ();
користувач1154664

10
@ user1154664 Насправді ви не можете. Використовуючи ExpectedException, ви можете перевірити лише те, що один метод кидає виняток, тому що, коли цей метод буде викликаний, тест припинить виконання, оскільки він кинув очікуваний виняток!
NamshubWriter

2
Ваше перше речення просто не відповідає дійсності. Під час використання ExpectedException, звичайне, що потрібно зробити, це встановити очікування безпосередньо перед рядком, який ви очікуєте, щоб викинути виняток. Таким чином, якщо попередній рядок видає виняток, це правило не запустить правило, і тест закінчиться невдачею.
Dawood ibn Kareem

212

Як було сказано раніше, у JUnit існує багато способів боротьби з винятками. Але у Java 8 є ще одна: використання 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

Розкриття інформації: я є автором блогу та проекту.


2
Мені подобається це рішення, але чи можу я завантажити його з Maven repo?
Селвін

@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 / ...
Vadzim

154

у червні існують чотири способи перевірки виключення.

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();
        }
    }
    
  • Ви також можете використовувати класичний спосіб спробувати / ловити, широко використовуваний в рамках 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, то вам сподобається 1-й
    • 2-й спосіб використовується, коли ви хочете лише перевірити тип винятку
    • перший і останній два використовуються тоді, коли ви хочете далі випробувати тестове повідомлення про виключення
    • якщо ви користуєтесь джунтом 3, то кращим є четвертий
  • для отримання додаткової інформації, ви можете прочитати цей документ та посібник користувача junit5 для детальної інформації.


6
Для мене це найкраща відповідь, вона охоплює всі шляхи дуже чітко, дякую! Особисто я продовжую використовувати 3-й варіант навіть для Junit4 для читабельності, щоб уникнути порожнього блоку вилову, ви також можете зловити тип Throwable і затвердити електронний текст
Ніколас Корнет

Чи можна використовувати очікуваневиключення для очікування перевіреного винятку?
miuser

Все це - це скупчення трьох найкращих відповідей. IMO, ця відповідь навіть не повинна була розміщуватися, якщо вона не додає нічого нового. Просто відповідь (популярне запитання) для представника. Досить марно.
Пол Самсота

впевнений, тому що ви можете передати будь-який тип, похідний від Trowableметоду ExpectedException.expect. будь ласка, дивіться, що це підпис . @miuser
Уолш

116

тл; д-р

  • post-JDK8: Використовуйте AssertJ або користувацькі лямбдаси для затвердження виняткової поведінки.

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

Незалежно від 4 червня чи JUnit 5.

довга історія

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

Але ці способи не є настільки витонченими і не поєднують з легкістю читання з іншими інструментами. Більше того, інструменти JUnit мають деякі підводні камені.

  1. Блок try- 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 : анулюйте очікуваневизнання

    Метод 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();

    Як ви бачите, що код справді простий, ви thenвиберете виняток у певному рядку, API - псевдонім, який використовуватиме API APIrtrt (подібний до використання assertThat(ex).hasNoCause()...). У якийсь момент проект покладався на FEST-Assert, родоначальника AssertJ . EDIT: Схоже, що проект готує підтримку Java 8 Lambdas.

    Наразі ця бібліотека має два недоліки:

    • На момент написання цього документу варто звернути увагу на те, що ця бібліотека базується на Mockito 1.x, оскільки вона створює знущання над тестуваним об’єктом за сценою. Оскільки Mockito все ще не оновлюється, ця бібліотека не може працювати з заключними класами чи заключними методами . І навіть якщо він базувався на Mockito 2 в поточній версії, для цього потрібно було б оголосити глобального макетодавця ( inline-mock-maker), що може бути не тим, що ви хочете, оскільки цей макет-виробник має інші недоліки, ніж звичайний виробник макетів.

    • Це вимагає ще однієї залежності від тесту.

    Ці питання не застосовуватимуться, коли бібліотека підтримує лямбда. Однак функціонал буде дублюватися набором інструментів 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 чи ні) раніше JDKs замість цього слід покладатися на try-catch блоків , навіть якщо вони відчувають себе незграбними.

Ця відповідь була скопійована з іншого запитання, яке не має однакової видимості, я той же автор.


1
Додавання org.junit.jupiter: junit-jupiter-engine: залежність 5.0.0-RC2 (на додаток до вже існуючого липня: junit: 4.12), щоб мати можливість використовувати assrtThrows, можливо, не є кращим рішенням, але це не спричинило жодного питання для мене.
НАРЕ

Я прихильник використання правила ExpectedException, але мене завжди непокоїло, що він розривається з AAA. Ви написали чудову статтю, щоб описати всі різні підходи, і ви неодмінно заохотили мене спробувати AssertJ :-) Дякую!
Пім Газеброк

@PimHazebroek дякую. API AssertJ досить багатий. На мій погляд, краще, що те, що пропонує 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. Вбудований у JUnit
  2. Ви отримуєте корисне повідомлення про виняток, якщо код у лямбда не викидає виняток, а стек-трек, якщо він видає інший виняток
  3. Стислі
  4. Дозволяє вашим тестам слідувати Arrange-Act-Assert
  5. Ви можете точно вказати, який код ви очікуєте, щоб викинути виняток
  6. Не потрібно перераховувати очікуваний виняток у throwsпункті
  7. Ви можете використовувати затверджені рамки твердження, щоб зробити твердження про спійманий виняток

Аналогічний метод буде доданий до org.junit AssertJUnit 4.13.


Цей підхід є чистим, але я не бачу, як це дозволяє нашому тесту слідувати "Arrange-Act-Assert", оскільки ми повинні перетворити частину "Act" у "assrtThrow", що є затвердженням.
Годинник

@Clockwork Лямбда - це «дія». Мета Arrange-Act-Assert полягає в тому, щоб зробити код чистим і простим (а отже, легким для розуміння та обслуговування). Як ви заявили, такий підхід чистий.
NamshubWriter

Я все ще сподівався, що зможу стверджувати кидок та виняток наприкінці тесту, хоча у частині "затвердити". При такому підході вам потрібно обернути вчинок в першу чергу, щоб вперше його зрозуміти.
Зведений

Це вимагатиме більше коду у кожному тесті, щоб зробити твердження. Це більше коду і може бути схильним до помилок.
NamshubWriter

43

Як щодо цього: Ловіть дуже загальний виняток, переконайтеся, що він виходить із блоку вилову, а потім запевняйте, що клас винятку - такий, який ви очікуєте від нього. Це твердження буде невдалим, якщо: a) виняток неправильного типу (наприклад, якщо ви отримали Null Pointer замість цього) та b) виняток ніколи не викидався.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

3
Крім того, ви не побачите, який виняток є у результатах тесту, коли настає день, коли тест закінчується.
jontejj

Це можна трохи покращити, змінивши спосіб ствердження в кінці. assertEquals(ExpectedException.class, e.getClass())покаже очікувані та фактичні значення, коли тест не вдасться.
Cypher


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, assert, що конфліктує з іменем юніта. докладніше про assrtJ кидок: 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();

Це менш дослівно, ніж правило очікуваного розгляду JUnit 4.7. Порівняно з рішенням, яке надає skaffman, ви можете вказати, у якому рядку коду ви очікуєте винятку. Я сподіваюся, що це допомагає.


Я думав про те, щоб зробити щось подібне, але, врешті-решт, виявив, що справжня сила очікуваного виразу полягає в тому, що ви не тільки можете вказати очікуваний виняток, але також можете вказати певні властивості винятку, такі як очікувана причина чи очікуване повідомлення.
Джейсон Томпсон

Я здогадуюсь, що це рішення має ті ж недоліки, що і макети? Наприклад, якщо 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 .


22

Ви також можете зробити це:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

12
У тестах JUnit краще використовувати Assert.fail(), не assertна випадок, якщо ваші тести працюють у середовищі, де твердження не ввімкнено.
NamshubWriter

14

IMHO, найкращий спосіб перевірити винятки в 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

Найбільш гнучку та елегантну відповідь на 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("");

    }

}

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();
    }
});

Нульові залежності: немає потреби в макіто, немає потреби в пауермоку; і добре працює з заключними класами.


Цікаво, але не підходить до AAA (Arrange Act Assert), де ви хочете виконати Акт та крок Assert фактично різними кроками.
Блн-Том

1
@ bln-tom Технічно це два різні кроки, вони просто не в такому порядку. ; p
Трейказ

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

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();

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

}

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());
}

3
Другий catchпроковтне слід стека, якщо якийсь інший виняток буде кинутий, втративши корисну інформацію
NamshubWriter

5

Додатково до того, що сказав NamShubWriter , переконайтесь, що:

  • Екземпляр очікуваного розгляду є загальнодоступним ( пов'язане запитання )
  • Очікувана ексцепція не приводиться у відповідь, наприклад, методом @Before. Цей пост чітко пояснює всі тонкощі порядку виконання JUnit.

Ви НЕ робити цього:

@Rule    
public ExpectedException expectedException;

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

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


4

Я рекомендую бібліотеку, assertj-coreщоб обробляти винятки в тесті Джуніт

У 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;

               }

    }

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


1

JUnit Framework має assertThrows()метод:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());

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);

Переваги:

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