Jak twierdzicie, że w testach JUnit 4 zgłaszany jest pewien wyjątek?


1998

Jak mogę używać JUnit4 idiomatycznie do testowania, czy jakiś kod zgłasza wyjątek?

Chociaż z pewnością mogę zrobić coś takiego:

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

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

  assertTrue(thrown);
}

Pamiętam, że istnieje adnotacja lub plik Assert.xyz lub coś , co jest znacznie mniej niezgrabne i znacznie bardziej zgodne z duchem JUnit dla tego rodzaju sytuacji.


21
Problem z każdym innym podejściem polega na tym, że niezmiennie kończą test po zgłoszeniu wyjątku. Z drugiej strony często nadal chcę dzwonić org.mockito.Mockito.verifyz różnymi parametrami, aby upewnić się, że pewne rzeczy się zdarzyły (takie, że usługa rejestratora została wywołana z poprawnymi parametrami) przed zgłoszeniem wyjątku.
ZeroOne,


6
@ZeroOne - W tym celu miałbym dwa różne testy - jeden dla wyjątku i jeden dla sprawdzenia interakcji z twoją próbą.
tddmonkey,

Jest na to sposób w JUnit 5, poniżej zaktualizowałem swoją odpowiedź.
Dilini Rajapaksha

Odpowiedzi:


2360

Zależy to od wersji JUnit i używanych bibliotek asercji.

Oryginalna odpowiedź JUnit <= 4.12brzmiała:

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

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

}

Chociaż odpowiedź https://stackoverflow.com/a/31826781/2986984 ma więcej opcji dla JUnit <= 4.12.

Odniesienie :


66
Ten fragment kodu nie będzie działać, jeśli spodziewasz się wyjątku tylko gdzieś w kodzie, a nie koca takiego jak ten.
Oh Chin Boon,

4
@skaffman To nie działałoby z org.junit.experimental.theories.Theory prowadzonym przez org.junit.experimental.theories.Theories
Artem Oboturov

74
Roy Osherove odradza takie testy wyjątków w sztuce testów jednostkowych , ponieważ wyjątek może znajdować się w dowolnym miejscu testu, a nie tylko w testowanej jednostce.
Kevin Wittek

21
Nie zgadzam się z @ Kiview / Roy Osherove. Moim zdaniem testy powinny dotyczyć zachowania, a nie implementacji. Testując, czy określona metoda może zgłosić błąd, wiążesz testy bezpośrednio z implementacją. Twierdziłbym, że testowanie w powyższej metodzie zapewnia bardziej wartościowy test. Zastrzegam, że dodam, że w tym przypadku przetestowałbym niestandardowy wyjątek, aby wiedzieć, że otrzymuję wyjątek, którego naprawdę chcę.
nickbdyer

5
Ani. Chcę przetestować zachowanie klasy. Co ważne, jeśli spróbuję odzyskać coś, czego nie ma, dostaję wyjątek. Fakt, że struktura danych ArrayListodpowiada, get()jest nieistotny. Gdybym w przyszłości zdecydował się przejść na prymitywną tablicę, musiałbym zmienić tę implementację testową. Struktura danych powinna być ukryta, aby test mógł skupić się na zachowaniu klasy .
nickbdyer

1315

Edycja: Teraz, gdy JUnit 5 i JUnit 4.13 zostały wydane, najlepszą opcją byłoby użycie Assertions.assertThrows() (dla JUnit 5) i Assert.assertThrows()(dla JUnit 4.13). Zobacz moją drugą odpowiedź aby poznać szczegóły.

Jeśli nie przeprowadziłeś migracji do JUnit 5, ale możesz korzystać z JUnit 4.7, możesz użyć ExpectedExceptionreguły:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

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

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Jest to o wiele lepsze niż @Test(expected=IndexOutOfBoundsException.class)ponieważ test zakończy się niepowodzeniem, jeśli IndexOutOfBoundsExceptionzostanie wcześniej rzuconyfoo.doStuff()

Zobacz ten artykuł szczegóły


14
@skaffman - Jeśli dobrze to zrozumiałem, wygląda to na wyjątek. oczekiwanie jest stosowane tylko w ramach jednego testu, a nie całej klasy.
Bacar

5
Jeśli wyjątek, który, jak się spodziewamy, zostanie rzucony, jest sprawdzonym wyjątkiem, czy powinniśmy dodać rzuty lub spróbować złapać lub przetestować tę sytuację w inny sposób?
Mohammad Jafar Mashhadi

5
@MartinTrummer Po foo.doStuff () nie powinien być uruchamiany żaden kod, ponieważ zgłoszony jest wyjątek i metoda zostaje zakończona. Posiadanie kodu po oczekiwanym wyjątku (z wyjątkiem zamykania zasobów w końcu) i tak jest nieprzydatne, ponieważ nigdy nie powinno zostać wykonane, jeśli wyjątek zostanie zgłoszony.
Jason Thompson

9
To najlepsze podejście. Są tu dwie zalety w porównaniu z rozwiązaniem skaffmana. Po pierwsze, ExpectedExceptionklasa ma sposoby dopasowania komunikatu wyjątku, a nawet napisania własnego elementu dopasowującego, który zależy od klasy wyjątku. Po drugie, możesz ustawić swoje oczekiwania bezpośrednio przed wierszem kodu, w którym chcesz zgłosić wyjątek - co oznacza, że ​​test zakończy się niepowodzeniem, jeśli niewłaściwy wiersz kodu zgłasza wyjątek; podczas gdy rozwiązanie skaffmana nie jest możliwe.
Dawood ibn Kareem

5
@MJafarMash, jeśli zaznaczony zostanie wyjątek, który chcesz zgłosić, to dodaj go do klauzuli throws metody testowej. To samo robisz za każdym razem, gdy testujesz metodę, która zadeklaruje zgłoszenie sprawdzonego wyjątku, nawet jeśli wyjątek ten nie zostanie uruchomiony w konkretnym przypadku testowym.
NamshubWriter

471

Zachowaj ostrożność, używając oczekiwanego wyjątku, ponieważ zapewnia on jedynie, że metoda zgłosiła ten wyjątek, a nie określoną linię kodu w teście.

Zwykle używam tego do testowania sprawdzania poprawności parametrów, ponieważ takie metody są zwykle bardzo proste, ale bardziej złożone testy mogą być lepiej obsługiwane z:

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

Zastosuj osąd.


95
Może jestem starą szkołą, ale nadal wolę to. Daje mi również miejsce do przetestowania samego wyjątku: czasami mam wyjątki z modułami pobierającymi określone wartości, lub po prostu mogę poszukać konkretnej wartości w komunikacie (np. Szukając „xyz” w komunikacie „nierozpoznany kod„ xyz ” „).
Rodney Gitzel

3
Myślę, że podejście NamshubWriter daje ci to, co najlepsze z obu światów.
Eddie,

4
Za pomocą ExpectedException można wywołać N wyjątek.expect na metodę, aby przetestować jak ten wyjątek.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); wyjątek.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); wyjątek.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664,

10
@ user1154664 W rzeczywistości nie możesz. Za pomocą ExpectedException możesz przetestować tylko, że jedna metoda zgłasza wyjątek, ponieważ po wywołaniu tej metody test przestanie działać, ponieważ zwrócił oczekiwany wyjątek!
NamshubWriter

2
Twoje pierwsze zdanie po prostu nie jest prawdziwe. Podczas używania ExpectedExceptionnormalną czynnością jest ustawienie oczekiwania bezpośrednio przed linią, w której ma zostać zgłoszony wyjątek. W ten sposób, jeśli wcześniejsza linia zgłosi wyjątek, nie uruchomi reguły i test zakończy się niepowodzeniem.
Dawood ibn Kareem

212

Jak już wcześniej wspomniano, istnieje wiele sposobów radzenia sobie z wyjątkami w JUnit. Ale w Javie 8 jest jeszcze jedna: użycie wyrażeń Lambda. Dzięki wyrażeniom Lambda możemy uzyskać następującą składnię:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown akceptuje interfejs funkcjonalny, którego instancje można tworzyć za pomocą wyrażeń lambda, referencji metod lub referencji konstruktora. assertThrown akceptujący ten interfejs będzie oczekiwać i będzie gotowy do obsługi wyjątku.

Jest to stosunkowo prosta, ale potężna technika.

Obejrzyj ten post na blogu opisujący tę technikę: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Kod źródłowy można znaleźć tutaj: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Ujawnienie: Jestem autorem bloga i projektu.


2
Podoba mi się to rozwiązanie, ale czy mogę je pobrać z repozytorium maven?
Selwyn

@Austeruster jedna implementacja tego pomysłu dostępna w Maven to stefanbirkner.github.io/vallado
NamshubWriter

6
@CristianoFontes prostsza wersja tego API jest przeznaczona dla JUnit 4.13. Zobacz github.com/junit-team/junit/commit/…
NamshubWriter

@RafalBorowiec technicznie new DummyService()::someMethodjest MethodHandle, ale to podejście działa równie dobrze z wyrażeniami lambda.
Andy

@NamshubWriter, wygląda na to, że junit 4.13 został porzucony na rzecz junit 5: stackoverflow.com/questions/156503/…
Vadzim

154

w junit istnieją cztery sposoby testowania wyjątku.

junit5.x

  • dla junit5.x, możesz użyć assertThrowsw następujący sposób

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

junit4.x

  • w przypadku junit4.x użyj opcjonalnego „oczekiwanego” atrybutu anulowania testu

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • w przypadku junit4.x użyj reguły 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();
        }
    }
  • możesz także użyć klasycznego sposobu try / catch, szeroko stosowanego w ramach 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.
        }
    }
  • więc

    • jeśli lubisz junit 5, to powinieneś polubić pierwszy
    • drugi sposób jest używany, gdy chcesz tylko przetestować typ wyjątku
    • pierwsze i ostatnie dwa są używane, gdy chcesz dalej testować komunikat o wyjątku
    • jeśli używasz junit 3, preferowany jest czwarty
  • Aby uzyskać więcej informacji, możesz przeczytać ten dokument i podręcznik użytkownika junit5 w celu uzyskania szczegółowych informacji.


6
Dla mnie to najlepsza odpowiedź, bardzo wyraźnie obejmuje wszystkie sposoby, dzięki! Osobiście nadal używam trzeciej opcji, nawet dla Junit4 dla czytelności, aby uniknąć pustego bloku catch, możesz także złapać Throwable i potwierdzić typ e
Nicolas Cornette

Czy można użyć ExpectedException, aby oczekiwać sprawdzonego wyjątku?
miuser

Wszystko to jest kumulacją trzech najlepszych odpowiedzi. IMO, ta odpowiedź nie powinna nawet zostać opublikowana, jeśli nie dodaje nic nowego. Po prostu odpowiadam (popularne pytanie) przedstawicielowi. Całkiem bezużyteczne.
Paul Samsotha

pewny, ponieważ można przekazać dowolny typ pochodzący od Trowablemetody ExpectedException.expect. zobacz jego podpis . @miuser
walsh

116

tl; dr

  • po JDK8: Użyj AssertJ lub niestandardowych lambd, aby potwierdzić wyjątkowe zachowanie.

  • pre-JDK8: Ja polecam ten stary dobry try- catchbloku. ( Nie zapomnij dodać fail()asercji przed catchblokiem )

Niezależnie od Junit 4 lub JUnit 5.

długa historia

Możliwe jest napisanie zrób to sam try - catchzablokuj lub użyj narzędzi JUnit ( @Test(expected = ...)lub@Rule ExpectedException funkcji reguły JUnit).

Ale te sposoby nie są tak eleganckie i nie łączą dobrze czytelności z innymi narzędziami. Co więcej, oprzyrządowanie JUnit ma pewne pułapki.

  1. try- catchblok trzeba napisać blok wokół badanego zachowania i napisać twierdzenie w bloku catch, że może być w porządku, ale wielu uważa, że ten styl przerywa przepływ odczytu testu. Musisz także napisać Assert.failna końcu trybloku. W przeciwnym razie test może pominąć jedną ze stron twierdzeń; PMD , findbugs lub Sonar wykryją takie problemy.

  2. Ta @Test(expected = ...)funkcja jest interesująca, ponieważ możesz napisać mniej kodu, a następnie napisanie tego testu jest prawdopodobnie mniej podatne na błędy kodowania. Jednak w niektórych obszarach brakuje tego podejścia.

    • Jeśli test wymaga sprawdzenia dodatkowych elementów wyjątku, takich jak przyczyna lub komunikat (dobre komunikaty wyjątków są naprawdę ważne, posiadanie dokładnego typu wyjątku może nie wystarczyć).
    • Ponieważ w metodzie spodziewane jest oczekiwanie, w zależności od tego, jak napisany jest testowany kod, niewłaściwa część kodu testowego może zgłosić wyjątek, co prowadzi do fałszywie dodatniego testu i nie jestem pewien, czy PMD , findbugs lub Sonar poda wskazówki dotyczące takiego kodu.

      @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. ExpectedExceptionZasada jest również próbą naprawić wcześniejsze zastrzeżenia, ale to jest trochę niewygodne w użyciu, ponieważ wykorzystuje styl oczekiwania, EasyMock użytkownicy wiedzą bardzo dobrze ten styl. Dla niektórych może być to wygodne, ale jeśli przestrzegasz zasad Behaviour Driven Development (BDD) lub Arrange Act Assert (AAA), ExpectedExceptionreguła nie będzie pasować do tego stylu pisania. Oprócz tego może to dotyczyć tego samego problemu co @Testdroga, w zależności od tego, gdzie stawiasz oczekiwania.

    @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
    }

    Nawet oczekiwany wyjątek jest umieszczony przed instrukcją testu, przerywa przepływ odczytu, jeśli testy są zgodne z BDD lub AAA.

    Zobacz także ten komentarz do JUnit autora ExpectedException. JUnit 4.13-beta-2 nawet przestaje działać w tym mechanizmie:

    Wyciągnij wniosek # 1519 : Przestarzałe oczekiwany wyjątek

    Metoda Assert.assertThrows zapewnia lepszy sposób weryfikacji wyjątków. Ponadto użycie ExpectedException jest podatne na błędy, gdy jest używane z innymi regułami, takimi jak TestWatcher, ponieważ w takim przypadku kolejność reguł jest ważna.

Tak więc powyższe opcje mają całą masę zastrzeżeń i wyraźnie nie są odporne na błędy kodera.

  1. Jest taki projekt, o którym zdałem sobie sprawę po utworzeniu tej odpowiedzi, która wygląda obiecująco, jest to wyjątek dla wszystkich .

    Jak mówi opis projektu, pozwala programistom pisać płynnym wierszem kodu przechwytującego wyjątek i oferuje ten wyjątek dla tego ostatniego stwierdzenia. I możesz użyć dowolnej biblioteki asercji, takiej jak Hamcrest lub AssertJ .

    Szybki przykład zaczerpnięty ze strony głównej:

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

    Jak widać, kod jest naprawdę prosty, thenwychwytujesz wyjątek w określonej linii, API jest aliasem, który będzie używał API AssertJ (podobnie jak przy użyciu assertThat(ex).hasNoCause()...). W pewnym momencie projekt polegał na FEST-Assert, przodku AssertJ . EDYCJA: Wygląda na to, że projekt przygotowuje obsługę Java 8 Lambdas.

    Obecnie ta biblioteka ma dwie wady:

    • W chwili pisania tego tekstu warto wspomnieć, że ta biblioteka oparta jest na Mockito 1.x, ponieważ tworzy próbkę testowanego obiektu za sceną. Ponieważ Mockito wciąż nie jest aktualizowany, ta biblioteka nie może współpracować z ostatecznymi klasami lub ostatecznymi metodami . I nawet jeśli był oparty na Mockito 2 w bieżącej wersji, wymagałoby to zadeklarowania globalnego makiety ( inline-mock-maker), co może nie być tym, czego chcesz, ponieważ ten makiet ma inne wady niż zwykły makieta.

    • Wymaga to kolejnej zależności testowej.

    Te problemy nie będą obowiązywać, gdy biblioteka będzie obsługiwać lambdas. Jednak funkcjonalność zostanie zduplikowana przez zestaw narzędzi AssertJ.

    Biorąc to wszystko pod uwagę, jeśli nie chcesz używać narzędzia do wychwytywania wyjątków, polecę stary dobry sposób bloku try- catchprzynajmniej do JDK7. A dla użytkowników JDK 8 wolisz korzystać z AssertJ, ponieważ oferuje więcej niż tylko wyjątki.

  2. Dzięki JDK8 lambdy wchodzą na scenę testową i okazały się interesującym sposobem na zachowanie wyjątkowego zachowania. AssertJ został zaktualizowany, aby zapewnić przyjemny, płynny interfejs API do zapewnienia wyjątkowego zachowania.

    I przykładowy test z 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. Dzięki prawie całkowitemu przepisaniu JUnit 5, asercje zostały nieco ulepszone , mogą okazać się interesujące jako nieszablonowy sposób na zapewnienie właściwego wyjątku. Ale tak naprawdę interfejs API asercji jest nadal trochę kiepski, na zewnątrz nie ma nic assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }

    Jak zauważyłeś, assertEqualswciąż powracavoid i jako takie nie pozwala na łączenie łańcuchów takich jak AssertJ.

    Również, jeśli pamiętasz starcie nazwy z Matcherlub Assert, bądź przygotowany na spotkanie tego samego starcia z Assertions.

Chciałbym podsumować, że dziś (2017-03-03) łatwość użycia AssertJ , wykrywalny API, szybkie tempo rozwoju i de facto zależność od testów jest najlepszym rozwiązaniem z JDK8 niezależnie od frameworka testowego (JUnit lub nie), wcześniejsze pakiety JDK powinny zamiast tego polegać na try-catch bloki nawet jeśli czują się niezgrabne.

Ta odpowiedź została skopiowana z innego pytania , które nie mają tej samej widoczności, jestem tym samym autorem.


1
Dodanie org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 zależność (oprócz już istniejącego junit: junit: 4.12), aby móc korzystać z assertThrows, być może nie jest preferowanym rozwiązaniem, ale nie spowodowało żadnego problemy dla mnie.
anre

Jestem fanem korzystania z reguły ExpectedException, ale zawsze przeszkadzało mi, że zrywa z AAA. Napisałeś świetny artykuł opisujący różne podejścia i zdecydowanie zachęciłeś mnie do wypróbowania AssertJ :-) Dzięki!
Pim Hazebroek

@PimHazebroek dzięki. Interfejs API AssertJ jest dość bogaty. Moim zdaniem lepsze jest to, co JUnit proponuje od razu po wyjęciu z pudełka.
Brice

64

Teraz, gdy JUnit 5 i JUnit 4.13 zostały wydane, najlepszą opcją byłoby użycie Assertions.assertThrows() (dla JUnit 5) i Assert.assertThrows()(dla JUnit 4.13). Zobacz Podręcznik użytkownika Junit 5 .

Oto przykład, który weryfikuje zgłoszony wyjątek i używa Prawdy, aby twierdzić o komunikacie wyjątku:

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

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

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

Zalety w stosunku do podejść przedstawionych w innych odpowiedziach to:

  1. Wbudowany w JUnit
  2. Otrzymasz przydatny komunikat wyjątku, jeśli kod w lambda nie zgłasza wyjątku, a stacktrace, jeśli zgłosi inny wyjątek
  3. Zwięzły
  4. Pozwala twoim testom postępować zgodnie z Arrange-Act-Assert
  5. Możesz dokładnie wskazać, jakiego kodu spodziewasz się zgłosić wyjątek
  6. Nie musisz wymieniać oczekiwanego wyjątku w throwsklauzuli
  7. Możesz użyć wybranej przez siebie struktury asercji, aby dokonać asercji na temat przechwyconego wyjątku

Podobna metoda zostanie dodana org.junit Assertw JUnit 4.13.


To podejście jest czyste, ale nie widzę, jak pozwala to naszemu testowi na wykonanie „Arrange-Act-Assert”, ponieważ musimy owinąć część „Act” w „assertThrow”, który jest asersem.
Mechaniczna

@Clockwork Lambda jest „aktem”. Celem Arrange-Act-Assert jest uczynienie kodu czystym i prostym (a zatem łatwym do zrozumienia i utrzymania). Jak powiedziałeś, takie podejście jest czyste.
NamshubWriter

Nadal miałem nadzieję, że uda mi się potwierdzić rzut i wyjątek na końcu testu, w części „twierdzić”. W tym podejściu musisz najpierw zawinąć akt, aby go najpierw złapać.
Mechaniczna

Wymagałoby to więcej kodu w każdym teście, aby wykonać asercję. To więcej kodu i byłoby podatne na błędy.
NamshubWriter

43

Co powiesz na to: Złap bardzo ogólny wyjątek, upewnij się, że wykracza on z bloku catch, a następnie upewnij się, że klasa wyjątku jest taka, jakiej się spodziewasz. To twierdzenie zakończy się niepowodzeniem, jeśli a) wyjątek jest niewłaściwego typu (np. Jeśli zamiast tego otrzymasz wskaźnik zerowy) ib) wyjątek nigdy nie został zgłoszony.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

3
Ponadto nie zobaczysz, jaki wyjątek jest w wynikach testu, gdy przyjdzie dzień, w którym test się nie powiedzie.
jontejj

Można to nieco poprawić, zmieniając sposób zapewniania na końcu. assertEquals(ExpectedException.class, e.getClass())pokaże oczekiwane i rzeczywiste wartości, gdy test się nie powiedzie.
Cypher

37

Rozwiązanie w stylu BDD : JUnit 4 + wyjątek Catch + AssertJ

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

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

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

}

Zależności

eu.codearte.catch-exception:catch-exception:2.0

36

Korzystanie z asercji AssertJ , której można używać razem z JUnit:

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

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

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

Jest to lepsze niż @Test(expected=IndexOutOfBoundsException.class)ponieważ gwarantuje, że oczekiwana linia w teście rzuciła wyjątek i pozwala sprawdzić więcej szczegółów na temat wyjątku, takich jak wiadomość, łatwiej:

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

Instrukcje Maven / Gradle tutaj.


najbardziej zwięzły sposób i nikt tego nie docenia, dziwne .. Mam tylko jeden problem z biblioteką assertJ, assert Taki konflikt nazw jest z junitem. więcej o assertJ throwby: JUnit: Testowanie wyjątków w Javie 8 i AssertJ 3.0.0 ~ Codeleak.pl
ycomp

@ycomp Cóż, jest to nowa odpowiedź na bardzo stare pytanie, więc różnica wyników jest zwodnicza.
weston

To prawdopodobnie najlepsze rozwiązanie, jeśli można używać Java 8 i AssertJ!
Pierre Henry

@ycomp Podejrzewam, że konflikt nazw może wynikać z projektu: dlatego biblioteka AssertJ zdecydowanie zachęca, aby nigdy nie używać JUnit assertThat, zawsze tej z AssertJ. Również metoda JUnit zwraca tylko „zwykły” typ, podczas gdy metoda AssertJ zwraca AbstractAssertpodklasę… pozwalającą na ciąg znaków metod jak wyżej (lub cokolwiek technicznego terminu na to…).
Mike Rodent

@weston właściwie właśnie użyłem twojej techniki w AssertJ 2.0.0. Bez wątpienia nie ma usprawiedliwienia dla braku aktualizacji, ale chociaż możesz chcieć wiedzieć.
Mike Rodent

33

Aby rozwiązać ten sam problem, stworzyłem mały projekt: http://code.google.com/p/catch-exception/

Za pomocą tego małego pomocnika piszesz

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

Jest to mniej szczegółowe niż reguła ExpectedException JUnit 4.7. W porównaniu do rozwiązania dostarczonego przez skaffman możesz określić, w której linii kodu oczekujesz wyjątku. Mam nadzieję, że to pomoże.


Myślałem też o zrobieniu czegoś takiego, ale ostatecznie odkryłem, że prawdziwa potęga ExpectedException polega na tym, że nie tylko możesz określić oczekiwany wyjątek, ale także możesz określić pewne właściwości wyjątku, takie jak oczekiwana przyczyna lub oczekiwany komunikat.
Jason Thompson

Domyślam się, że to rozwiązanie ma te same wady co makiety? Na przykład, czy footo się finalnie powiedzie, ponieważ nie możesz proxy foo?
Tom

Tom, jeśli doStuff () jest częścią interfejsu, podejście proxy będzie działać. W przeciwnym razie to podejście zawiedzie, masz rację.
rwitzel

31

Aktualizacja: JUnit5 ma ulepszenie do testowania wyjątków:assertThrows .

następujący przykład pochodzi z: Podręcznik użytkownika Junit 5

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

Oryginalna odpowiedź przy użyciu JUnit 4.

Istnieje kilka sposobów sprawdzenia, czy zgłoszony jest wyjątek. Omówiłem również poniższe opcje w moim poście Jak pisać świetne testy jednostkowe z JUnit

Ustaw expectedparametr @Test(expected = FileNotFoundException.class).

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

Za pomocą 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!"));
    }

}

Testowanie z ExpectedExceptionregułą.

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

Możesz przeczytać więcej o testowaniu wyjątków na wiki JUnit4 dla testów wyjątków i bad.robot - Oczekiwanie na regułę JUnit wyjątków .


22

Możesz także to zrobić:

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

12
W testach JUnit lepiej jest go używać Assert.fail(), nie assertna wypadek, gdyby testy były wykonywane w środowisku, w którym asercje nie są włączone.
NamshubWriter

14

IMHO, najlepszym sposobem sprawdzenia wyjątków w JUnit jest wzorzec 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()); 
}

assertTrueMoże być nieco silny dla niektórych osób, więc assertThat(e.getMessage(), containsString("the message");może być korzystne.



13

Najbardziej elastyczną i elegancką odpowiedź na Junit 4 znalazłem na blogu Mkyong . Ma elastyczność try/catchkorzystania z @Ruleadnotacji. Podoba mi się to podejście, ponieważ możesz odczytać określone atrybuty niestandardowego wyjątku.

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

Wypróbowałem wiele metod tutaj, ale były one albo skomplikowane, albo nie całkiem spełniały moje wymagania. W rzeczywistości można napisać metodę pomocniczą po prostu:

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

Użyj tego w ten sposób:

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

Zero zależności: brak potrzeby mockito, brak potrzeby powermock; i działa dobrze z końcowymi klasami.


Interesujące, ale nie pasuje do AAA (Arrange Act Assert), gdzie chcesz wykonać Akt i krok Assert w faktycznie różnych krokach.
bln-tom

1
@ bln-tom Technicznie są to dwa różne kroki, po prostu nie są w tej kolejności. ; p
Trejkaz

10

Rozwiązanie Java 8

Jeśli potrzebujesz rozwiązania, które:

  • Wykorzystuje lambd Java 8
  • Czy nie zależy od jakiejkolwiek magii JUnit
  • Umożliwia sprawdzenie wielu wyjątków w ramach jednej metody testowej
  • Sprawdza, czy wyjątek jest zgłaszany przez określony zestaw wierszy w metodzie testowej zamiast nieznanego wiersza w całej metodzie testowej
  • Daje rzeczywisty obiekt wyjątku, który został zgłoszony, aby można go było dalej badać

Oto funkcja narzędzia, którą napisałem:

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.
}

( wzięte z mojego bloga )

Użyj go w następujący sposób:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}


8

W moim przypadku zawsze otrzymuję RuntimeException z db, ale komunikaty różnią się. I wyjątek należy odpowiednio obsłużyć. Oto jak to przetestowałem:

@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
przed wierszem } catch (należy wstawićfail("no exception thrown");
Daniel Alder,

6

Po prostu utwórz Matchera, który można wyłączyć i włączyć w następujący sposób:

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

Aby go użyć:

dodaj public ExpectedException exception = ExpectedException.none();, a następnie:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();

6

W JUnit 4 lub nowszym możesz przetestować wyjątki w następujący sposób

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


zapewnia to wiele funkcji, które można wykorzystać do ulepszenia naszych testów JUnit.
Jeśli widzisz poniższy przykład, testuję 3 rzeczy na wyjątku.

  1. Rodzaj zgłaszanego wyjątku
  2. Komunikat o wyjątku
  3. Przyczyna wyjątku


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

Możemy użyć niepowodzenia asercji po metodzie, która musi zwrócić wyjątek:

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
Drugi catch
połknąłby

5

Oprócz tego, co powiedział NamShubWriter , upewnij się, że:

  • Wystąpienie ExpectedException jest publiczne ( powiązane pytanie )
  • Oczekiwany wyjątek nie jest utworzony, powiedzmy, metodą @Before. Ten post jasno wyjaśnia wszystkie zawiłości kolejności wykonywania JUnit.

Czy nie to zrobić:

@Rule    
public ExpectedException expectedException;

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

Wreszcie, ten post na blogu jasno ilustruje, jak stwierdzić, że został zgłoszony pewien wyjątek.


4

Polecam bibliotekę assertj-coreobsługi wyjątku w teście junit

W java 8:

//given

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

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);

2

Rozwiązanie Junit4 z Java8 ma korzystać z tej funkcji:

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

Użycie jest wtedy:

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

Zauważ, że jedynym ograniczeniem jest użycie finalodwołania do obiektu w wyrażeniu lambda. To rozwiązanie pozwala kontynuować testowanie asercji zamiast oczekiwać możliwości zastosowania na poziomie metody za pomocą @Test(expected = IndexOutOfBoundsException.class)rozwiązania.


1

Weźmy na przykład, że chcesz napisać Junit dla niżej wymienionego fragmentu kodu

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

Powyższy kod służy do testowania nieznanych wyjątków, które mogą wystąpić, a poniżej do potwierdzenia wyjątku za pomocą niestandardowego komunikatu.

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

               }

    }

Oto inny sposób sprawdzenia, czy metoda zgłosiła poprawny wyjątek, czy nie.


1

Framework JUnit ma assertThrows()metodę:

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

0

W Javie 8 możesz utworzyć metodę, która pobierze kod do sprawdzenia i oczekiwany wyjątek jako parametry:

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

a następnie w teście:

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

Korzyści:

  • nie poleganie na żadnej bibliotece
  • kontrola zlokalizowana - bardziej precyzyjna i pozwala mieć wiele takich twierdzeń w ramach jednego testu, jeśli to konieczne
  • łatwy w użyciu
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.