Wie behaupten Sie, dass in JUnit 4-Tests eine bestimmte Ausnahme ausgelöst wird?

1999

Wie kann ich JUnit4 idiomatisch verwenden, um zu testen, ob ein Code eine Ausnahme auslöst?

Während ich so etwas sicher machen kann:

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

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

  assertTrue(thrown);
}

Ich erinnere mich, dass es eine Annotation oder eine Assert.xyz oder etwas gibt , das für diese Art von Situationen weit weniger klobig und viel mehr im Geiste von JUnit ist.

SCdF
quelle
21
Das Problem bei jedem anderen Ansatz ist jedoch, dass der Test immer beendet wird, sobald die Ausnahme ausgelöst wurde. Andererseits möchte ich oft noch org.mockito.Mockito.verifymit verschiedenen Parametern aufrufen , um sicherzustellen, dass bestimmte Dinge geschehen sind (z. B. dass ein Protokollierungsdienst mit den richtigen Parametern aufgerufen wurde), bevor die Ausnahme ausgelöst wurde.
ZeroOne
5
Sie können sehen, wie Ausnahmen auf der JUnit-Wiki-Seite github.com/junit-team/junit/wiki/Exception-testing
PhoneixS
6
@ZeroOne - Dafür hätte ich zwei verschiedene Tests - einen für die Ausnahme und einen zur Überprüfung der Interaktion mit Ihrem Mock.
tddmonkey
Es gibt eine Möglichkeit, dies mit JUnit 5 zu tun. Ich habe meine Antwort unten aktualisiert.
Dilini Rajapaksha

Antworten:

2363

Dies hängt von der JUnit-Version und den von Ihnen verwendeten Assert-Bibliotheken ab.

Die ursprüngliche Antwort für JUnit <= 4.12war:

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

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

}

Die Antwort https://stackoverflow.com/a/31826781/2986984 bietet jedoch mehr Optionen für JUnit <= 4.12.

Referenz :

Skaffman
quelle
66
Dieser Code funktioniert nicht, wenn Sie eine Ausnahme nur irgendwo in Ihrem Code erwarten und keine Decke wie diese.
Oh Chin Boon
4
@skaffman Dies würde nicht mit org.junit.experimental.theories.Theory funktionieren, die von org.junit.experimental.theories.Theories ausgeführt wird
Artem Oboturov
74
Roy Osherove rät von dieser Art von Ausnahmetests in der Kunst des Komponententests ab , da sich die Ausnahme möglicherweise irgendwo innerhalb des Tests und nicht nur innerhalb des zu testenden Geräts befindet.
Kevin Wittek
21
Ich bin nicht einverstanden mit @ Kiview / Roy Osherove. Meiner Ansicht nach sollten Tests auf Verhalten und nicht auf Implementierung ausgerichtet sein. Indem Sie testen, ob eine bestimmte Methode einen Fehler auslösen kann, binden Sie Ihre Tests direkt an die Implementierung. Ich würde argumentieren, dass das Testen mit der oben gezeigten Methode einen wertvolleren Test darstellt. Die Einschränkung, die ich hinzufügen möchte, ist, dass ich in diesem Fall auf eine benutzerdefinierte Ausnahme testen würde, damit ich weiß, dass ich die Ausnahme erhalte, die ich wirklich möchte.
Nickbdyer
6
Weder. Ich möchte das Verhalten der Klasse testen. Wichtig ist, dass ich eine Ausnahme bekomme, wenn ich versuche, etwas abzurufen, das nicht vorhanden ist. Die Tatsache, dass die Datenstruktur ArrayListdarauf reagiert, get()ist irrelevant. Wenn ich mich in Zukunft für ein primitives Array entscheiden würde, müsste ich diese Testimplementierung ändern. Die Datenstruktur sollte ausgeblendet sein, damit sich der Test auf das Verhalten der Klasse konzentrieren kann .
Nickbdyer
1316

Bearbeiten: Nachdem JUnit 5 und JUnit 4.13 veröffentlicht wurden, ist die beste Option die Verwendung Assertions.assertThrows() (für JUnit 5) und Assert.assertThrows()(für JUnit 4.13). Siehe meine andere Antwort für Details.

Wenn Sie nicht auf JUnit 5 migriert sind, aber JUnit 4.7 verwenden können, können Sie die ExpectedExceptionRegel verwenden:

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

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

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

Dies ist viel besser als @Test(expected=IndexOutOfBoundsException.class)weil der Test fehlschlägt, wenn IndexOutOfBoundsExceptioner zuvor geworfen wirdfoo.doStuff()

Sehen Sie diesen Artikel für weitere Details

NamshubWriter
quelle
14
@skaffman - Wenn ich das richtig verstanden habe, sieht es so aus, als würde die Ausnahme.expect nur innerhalb eines Tests angewendet, nicht der gesamten Klasse.
Bacar
5
Wenn die Ausnahme, von der wir erwarten, dass sie ausgelöst wird, eine aktivierte Ausnahme ist, sollten wir Würfe hinzufügen oder versuchen, diese Situation zu fangen oder auf andere Weise zu testen?
Mohammad Jafar Mashhadi
5
@MartinTrummer Nach foo.doStuff () sollte kein Code ausgeführt werden, da die Ausnahme ausgelöst und die Methode beendet wird. Code nach einer erwarteten Ausnahme zu haben (mit Ausnahme des Schließens von Ressourcen in einer endgültigen Ausnahme) ist ohnehin nicht hilfreich, da er niemals ausgeführt werden sollte, wenn die Ausnahme ausgelöst wird.
Jason Thompson
9
Dies ist der beste Ansatz. Hier gibt es zwei Vorteile gegenüber der Skaffman-Lösung. Erstens hat die ExpectedExceptionKlasse Möglichkeiten, die Nachricht der Ausnahme abzugleichen oder sogar einen eigenen Matcher zu schreiben, der von der Klasse der Ausnahme abhängt. Zweitens können Sie Ihre Erwartung unmittelbar vor der Codezeile festlegen, von der Sie erwarten, dass sie die Ausnahme auslöst. Dies bedeutet, dass Ihr Test fehlschlägt, wenn die falsche Codezeile die Ausnahme auslöst. Mit der Lösung von Skaffman gibt es keine Möglichkeit, dies zu tun.
Dawood ibn Kareem
5
@MJafarMash Wenn die Ausnahme, die Sie auslösen möchten, aktiviert ist, fügen Sie diese Ausnahme der throw-Klausel der Testmethode hinzu. Sie tun dies jedes Mal, wenn Sie eine Methode testen, die deklariert ist, um eine aktivierte Ausnahme auszulösen, auch wenn die Ausnahme im jeweiligen Testfall nicht ausgelöst wird.
NamshubWriter
472

Seien Sie vorsichtig mit der erwarteten Ausnahme, da nur behauptet wird, dass die Methode diese Ausnahme ausgelöst hat, nicht eine bestimmte Codezeile im Test.

Ich neige dazu, dies zum Testen der Parametervalidierung zu verwenden, da solche Methoden normalerweise sehr einfach sind, aber komplexere Tests möglicherweise besser bedient werden mit:

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

Urteil anwenden.

Daveb
quelle
95
Vielleicht bin ich alte Schule, aber ich bevorzuge das immer noch. Es gibt mir auch die Möglichkeit, die Ausnahme selbst zu testen: Manchmal habe ich Ausnahmen mit Gettern für bestimmte Werte, oder ich suche einfach nach einem bestimmten Wert in der Nachricht (z. B. Suche nach "xyz" in der Nachricht "nicht erkannter Code 'xyz'). ").
Rodney Gitzel
3
Ich denke, der Ansatz von NamshubWriter bietet Ihnen das Beste aus beiden Welten.
Eddie
4
Mit ExpectedException können Sie N exception.expect pro Methode aufrufen, um wie folgt zu testen: exception.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664
10
@ user1154664 Eigentlich kannst du nicht. Mit ExpectedException können Sie nur testen, ob eine Methode eine Ausnahme auslöst. Wenn diese Methode aufgerufen wird, wird der Test nicht mehr ausgeführt, da die erwartete Ausnahme ausgelöst wurde.
NamshubWriter
2
Dein erster Satz ist einfach nicht wahr. Bei der Verwendung ExpectedExceptionist es normal, die Erwartung unmittelbar vor der Zeile festzulegen, in der die Ausnahme ausgelöst werden soll. Auf diese Weise wird die Regel nicht ausgelöst, wenn eine frühere Zeile die Ausnahme auslöst, und der Test schlägt fehl.
Dawood ibn Kareem
213

Wie bereits erwähnt, gibt es in JUnit viele Möglichkeiten, mit Ausnahmen umzugehen. Bei Java 8 gibt es jedoch noch eines: die Verwendung von Lambda-Ausdrücken. Mit Lambda Expressions können wir eine Syntax wie diese erreichen:

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

assertThrown akzeptiert eine funktionale Schnittstelle, deren Instanzen mit Lambda-Ausdrücken, Methodenreferenzen oder Konstruktorreferenzen erstellt werden können. assertThrown, das diese Schnittstelle akzeptiert, erwartet und ist bereit, eine Ausnahme zu behandeln.

Dies ist eine relativ einfache und dennoch leistungsstarke Technik.

Schauen Sie sich diesen Blog-Beitrag an, der diese Technik beschreibt: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Den Quellcode finden Sie hier: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Offenlegung: Ich bin der Autor des Blogs und des Projekts.

Rafal Borowiec
quelle
2
Ich mag diese Lösung, aber kann ich sie von einem Maven Repo herunterladen?
Selwyn
@ Airuster Eine Implementierung dieser Idee, die auf Maven verfügbar ist, ist stefanbirkner.github.io/vallado
NamshubWriter
6
@CristianoFontes Eine einfachere Version dieser API ist für JUnit 4.13 vorgesehen. Siehe github.com/junit-team/junit/commit/…
NamshubWriter
@ RafalBorowiec ist technisch gesehen new DummyService()::someMethodein MethodHandle, aber dieser Ansatz funktioniert genauso gut mit Lambda-Ausdrücken.
Andy
@NamshubWriter, es scheint, dass Junit 4.13 zugunsten von Junit 5 aufgegeben wurde: stackoverflow.com/questions/156503/…
Vadzim
154

In junit gibt es vier Möglichkeiten, eine Ausnahme zu testen.

junit5.x

  • Für junit5.x können Sie assertThrowsFolgendes verwenden

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

junit4.x

  • Verwenden Sie für junit4.x das optionale Attribut 'erwartet' der Testannonation

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • Verwenden Sie für junit4.x die ExpectedException-Regel

    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();
        }
    }
  • Sie können auch den klassischen Try / Catch-Weg verwenden, der unter dem Junit 3-Framework weit verbreitet ist

    @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.
        }
    }
  • damit

    • Wenn du Junit 5 magst, dann solltest du den 1. mögen
    • Der zweite Weg wird verwendet, wenn Sie nur den Ausnahmetyp testen möchten
    • Die ersten und letzten beiden werden verwendet, wenn Sie eine weitere Testausnahmemeldung wünschen
    • Wenn Sie Junit 3 verwenden, wird der 4. bevorzugt
  • Weitere Informationen finden Sie in diesem Dokument und im junit5-Benutzerhandbuch .

Walsh
quelle
6
Für mich ist dies die beste Antwort, sie deckt alle Wege sehr klar ab, danke! Persönlich verwende ich die 3. Option auch mit Junit4 aus Gründen der Lesbarkeit weiter. Um einen leeren Fangblock zu vermeiden, können Sie auch Throwable fangen und den Typ e
Nicolas Cornette am
Ist es möglich, ExpectedException zu verwenden, um eine aktivierte Ausnahme zu erwarten?
Miuser
Alles, was es ist, ist eine Ansammlung der drei wichtigsten Antworten. IMO, diese Antwort hätte nicht einmal veröffentlicht werden dürfen, wenn sie nichts Neues hinzufügt. Nur eine Antwort (eine beliebte Frage) für den Repräsentanten. Ziemlich nutzlos.
Paul Samsotha
sicher, weil Sie jeden von abgeleiteten Typ Trowablean die Methode übergeben können ExpectedException.expect. Bitte sehen Sie die Unterschrift . @miuser
Walsh
116

tl; dr

  • Post-JDK8: Verwenden Sie AssertJ oder benutzerdefinierte Lambdas, um außergewöhnliches Verhalten zu behaupten .

  • Pre-JDK8: Ich werde die alten gut empfehlen try- catchBlock. ( Vergessen Sie nicht, fail()vor dem catchBlock eine Zusicherung hinzuzufügen. )

Unabhängig von Junit 4 oder JUnit 5.

die lange Geschichte

Es ist möglich , sich selbst zu schreiben , ein do it yourself try - catchBlock oder verwenden Sie die JUnit - Tools ( @Test(expected = ...)oder die @Rule ExpectedExceptionFunktion JUnit - Regel).

Diese Methoden sind jedoch nicht so elegant und lassen sich in Bezug auf die Lesbarkeit nicht gut mit anderen Tools kombinieren. Darüber hinaus weist JUnit Tooling einige Fallstricke auf.

  1. Der try- catchBlock Sie müssen den Block um das getestete Verhalten schreiben und die Behauptung in den catch-Block schreiben. Das mag in Ordnung sein, aber viele finden, dass dieser Stil den Lesefluss eines Tests unterbricht. Außerdem müssen Sie Assert.failam Ende des tryBlocks ein schreiben . Andernfalls kann der Test eine Seite der Behauptungen verfehlen. PMD , Findbugs oder Sonar werden solche Probleme erkennen.

  2. Die @Test(expected = ...)Funktion ist interessant, da Sie weniger Code schreiben können und das Schreiben dieses Tests angeblich weniger anfällig für Codierungsfehler ist. Aber dieser Ansatz ist in einigen Bereichen fehlt.

    • Wenn der Test zusätzliche Dinge für die Ausnahme wie die Ursache oder die Nachricht überprüfen muss (gute Ausnahmemeldungen sind wirklich wichtig, ein genauer Ausnahmetyp reicht möglicherweise nicht aus).
    • Auch wenn die Erwartung in die Methode gestellt wird, kann der falsche Teil des Testcodes abhängig davon, wie der getestete Code geschrieben wird, die Ausnahme auslösen , was zu einem falsch positiven Test führt, und ich bin nicht sicher, ob PMD , Findbugs oder Sonar gibt Hinweise zu diesem Code.

      @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. Die ExpectedExceptionRegel ist auch ein Versuch, die vorherigen Einschränkungen zu beheben. Die Verwendung ist jedoch etwas umständlich, da ein Erwartungsstil verwendet wird. EasyMock- Benutzer kennen diesen Stil sehr gut. Für einige mag es praktisch sein, aber wenn Sie die Prinzipien von Behavior Driven Development (BDD) oder Arrange Act Assert (AAA) befolgen,ExpectedException passt die Regel nicht in diesen Schreibstil. Abgesehen davon kann es unter dem gleichen Problem wie der @TestWeg leiden , je nachdem, wo Sie die Erwartung platzieren.

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

    Selbst wenn die erwartete Ausnahme vor der Testanweisung steht, wird Ihr Lesefluss unterbrochen, wenn die Tests BDD oder AAA folgen.

    Siehe auch diese Kommentarausgabe zu JUnit des Autors von ExpectedException. JUnit 4.13-beta-2 lehnt diesen Mechanismus sogar ab:

    Pull-Anforderung Nr. 1519 : Verfallene ExpectedException

    Die Methode Assert.assertThrows bietet eine bessere Möglichkeit, Ausnahmen zu überprüfen. Darüber hinaus ist die Verwendung von ExpectedException fehleranfällig, wenn sie mit anderen Regeln wie TestWatcher verwendet wird, da in diesem Fall die Reihenfolge der Regeln wichtig ist.

Diese oben genannten Optionen haben also alle Vorbehalte und sind eindeutig nicht immun gegen Codiererfehler.

  1. Es gibt ein Projekt, auf das ich aufmerksam geworden bin, nachdem ich diese Antwort erstellt habe, das vielversprechend aussieht. Es ist eine Ausnahme .

    Wie in der Beschreibung des Projekts angegeben, konnte ein Codierer eine fließende Codezeile schreiben, die die Ausnahme abfängt, und diese Ausnahme für die letztere Behauptung anbieten. Und Sie können jede Assertionsbibliothek wie Hamcrest oder AssertJ verwenden .

    Ein schnelles Beispiel von der Homepage:

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

    Wie Sie sehen können, ist der Code wirklich einfach. Sie fangen die Ausnahme in einer bestimmten Zeile ab. Die thenAPI ist ein Alias, der AssertJ-APIs verwendet (ähnlich wie bei der Verwendung assertThat(ex).hasNoCause()...). Irgendwann stützte sich das Projekt auf FEST-Assert, den Vorfahren von AssertJ . EDIT: Es scheint, dass das Projekt eine Java 8 Lambdas-Unterstützung erstellt.

    Derzeit weist diese Bibliothek zwei Mängel auf:

    • Zum Zeitpunkt dieses Schreibens ist es bemerkenswert zu sagen, dass diese Bibliothek auf Mockito 1.x basiert, da sie ein Modell des getesteten Objekts hinter den Kulissen erstellt. Da Mockito immer noch nicht aktualisiert wird, kann diese Bibliothek nicht mit endgültigen Klassen oder endgültigen Methoden arbeiten . Und selbst wenn es in der aktuellen Version auf Mockito 2 basiert, müsste ein globaler Mock Maker ( inline-mock-maker) deklariert werden , was möglicherweise nicht Ihren Wünschen entspricht, da dieser Mock Maker andere Nachteile hat als der reguläre Mock Maker.

    • Es erfordert noch eine weitere Testabhängigkeit.

    Diese Probleme treten nicht auf, sobald die Bibliothek Lambdas unterstützt. Die Funktionalität wird jedoch vom AssertJ-Toolset dupliziert.

    Unter Berücksichtigung aller Aspekte, wenn Sie das Catch-Exception-Tool nicht verwenden möchten, empfehle ich den alten guten Weg des try- catchBlocks, zumindest bis zum JDK7. Und für JDK 8-Benutzer bevorzugen Sie möglicherweise die Verwendung von AssertJ, da es möglicherweise mehr als nur das Festlegen von Ausnahmen bietet.

  2. Mit dem JDK8 betreten Lambdas die Testszene und haben sich als interessante Möglichkeit erwiesen, außergewöhnliches Verhalten zu behaupten. AssertJ wurde aktualisiert, um eine flüssige API bereitzustellen, mit der außergewöhnliches Verhalten bestätigt werden kann.

    Und ein Beispieltest mit 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. Mit einer nahezu vollständigen Neufassung von JUnit 5 wurden die Behauptungen ein wenig verbessert . Sie können sich als sofort einsatzbereit erweisen, um eine ordnungsgemäße Ausnahme zu behaupten. Aber wirklich, die Assertion-API ist immer noch ein bisschen schlecht, da draußen ist nichts assertThrows.

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

    Wie Sie bemerkt haben, assertEqualskehrt es immer noch zurück voidund erlaubt daher keine Verkettung von Behauptungen wie AssertJ.

    Auch wenn Sie sich an Namenskonflikte mit Matcheroder erinnern Assert, sollten Sie darauf vorbereitet sein, denselben Konflikt mit zu treffen Assertions.

Ich möchte zum Schluss kommen, dass heute ( 03.03.2017 ) AssertJs Benutzerfreundlichkeit, erkennbare API, das schnelle Entwicklungstempo und als De-facto- Testabhängigkeit die beste Lösung mit JDK8 sind, unabhängig vom Testframework (JUnit) oder nicht), frühere JDKs sollten sich stattdessen auf try-catch Blöcke verlassen, selbst wenn sie sich klobig fühlen.

Diese Antwort wurde von einer anderen Frage kopiert , die nicht die gleiche Sichtbarkeit hat. Ich bin der gleiche Autor.

Brice
quelle
1
Das Hinzufügen der Abhängigkeit org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 (zusätzlich zu der bereits vorhandenen junit: junit: 4.12) zur Verwendung von assertThrows ist möglicherweise nicht die bevorzugte Lösung, hat jedoch keine verursacht Probleme für mich.
Anre
Ich bin ein Fan der ExpectedException-Regel, aber es hat mich immer gestört, dass sie mit AAA bricht. Sie haben einen ausgezeichneten Artikel geschrieben, um all die verschiedenen Ansätze zu beschreiben, und Sie haben mich definitiv ermutigt, AssertJ auszuprobieren :-) Danke!
Pim Hazebroek
@PimHazebroek danke. AssertJ API ist ziemlich reichhaltig. Besser meiner Meinung nach das, was JUnit sofort vorschlägt.
Brice
64

Nachdem JUnit 5 und JUnit 4.13 veröffentlicht wurden, ist die beste Option die Verwendung Assertions.assertThrows() (für JUnit 5) und Assert.assertThrows()(für JUnit 4.13). Siehe das Junit 5-Benutzerhandbuch .

Hier ist ein Beispiel, das überprüft, ob eine Ausnahme ausgelöst wird, und mithilfe von Truth Aussagen über die Ausnahmemeldung macht:

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

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

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

Die Vorteile gegenüber den Ansätzen in den anderen Antworten sind:

  1. Eingebaut in JUnit
  2. Sie erhalten eine nützliche Ausnahmemeldung, wenn der Code im Lambda keine Ausnahme auslöst, und eine Stapelverfolgung, wenn eine andere Ausnahme ausgelöst wird
  3. Prägnant
  4. Ermöglicht Ihren Tests, Arrange-Act-Assert zu folgen
  5. Sie können genau angeben, mit welchem ​​Code Sie die Ausnahme auslösen möchten
  6. Sie müssen die erwartete Ausnahme nicht in der throwsKlausel auflisten
  7. Sie können das Assertion-Framework Ihrer Wahl verwenden, um Assertions über die abgefangene Ausnahme zu erstellen

Eine ähnliche Methode wird org.junit Assertin JUnit 4.13 hinzugefügt .

NamshubWriter
quelle
Dieser Ansatz ist sauber, aber ich sehe nicht, wie unser Test "Arrange-Act-Assert" folgen kann, da wir den Teil "Act" in einen "assertThrow" einschließen müssen, der eine Behauptung ist.
Uhrwerk
@Clockwork Das Lambda ist der "Akt". Das Ziel von Arrange-Act-Assert ist es, den Code sauber und einfach (und daher leicht zu verstehen und zu warten) zu machen. Wie Sie sagten, ist dieser Ansatz sauber.
NamshubWriter
Ich hatte immer noch gehofft, dass ich den Wurf und die Ausnahme am Ende des Tests im Teil "Assert" behaupten konnte. Bei diesem Ansatz müssen Sie die Handlung in eine erste Behauptung einschließen, um sie zuerst zu erfassen.
Uhrwerk
Das würde mehr Code in jedem Test erfordern, um die Behauptung auszuführen. Das ist mehr Code und wäre fehleranfällig.
NamshubWriter
43

Wie wäre es damit: Fangen Sie eine sehr allgemeine Ausnahme ab, stellen Sie sicher, dass sie aus dem catch-Block herauskommt, und behaupten Sie dann, dass die Klasse der Ausnahme so ist, wie Sie es erwarten. Diese Behauptung schlägt fehl, wenn a) die Ausnahme vom falschen Typ ist (z. B. wenn Sie stattdessen einen Nullzeiger erhalten haben) und b) die Ausnahme nie ausgelöst wurde.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}
Johan
quelle
3
Außerdem sehen Sie in den Testergebnissen nicht, welche Art von Ausnahme ex in den Testergebnissen enthalten ist, wenn der Tag kommt, an dem der Test fehlschlägt.
Jontejj
Dies kann ein wenig verbessert werden, indem Sie ändern, wie Sie am Ende behaupten. assertEquals(ExpectedException.class, e.getClass())zeigt Ihnen die erwarteten und tatsächlichen Werte an, wenn der Test fehlschlägt.
Cypher
37

BDD- Lösung: JUnit 4 + Catch Exception + AssertJ

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

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

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

}

Abhängigkeiten

eu.codearte.catch-exception:catch-exception:2.0
MariuszS
quelle
36

Verwenden einer AssertJ- Assertion, die neben JUnit verwendet werden kann:

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

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

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

Es ist besser als @Test(expected=IndexOutOfBoundsException.class)weil es garantiert, dass die erwartete Zeile im Test die Ausnahme ausgelöst hat und Sie mehr Details über die Ausnahme, wie z. B. eine Nachricht, einfacher überprüfen können:

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

Maven / Gradle Anleitung hier.

Weston
quelle
auf prägnanteste Weise und niemand schätzt es, seltsam. Ich habe nur ein Problem mit der assertJ-Bibliothek, assertThat, das in Bezug auf den Namen mit dem von junit in Konflikt steht. mehr über assertJ throwby: JUnit: Testen von Ausnahmen mit Java 8 und AssertJ 3.0.0 ~ Codeleak.pl
ycomp
@ycomp Nun, es ist eine neue Antwort auf eine sehr alte Frage, daher täuscht der Unterschied in der Punktzahl.
Weston
Das ist wahrscheinlich die beste Lösung, wenn man Java 8 und AssertJ verwenden kann!
Pierre Henry
@ycomp Ich vermute, dass dieser Namenskonflikt beabsichtigt ist: Die AssertJ-Bibliothek empfiehlt Ihnen daher dringend, niemals die JUnit assertThat, sondern immer die AssertJ-Bibliothek zu verwenden. Außerdem gibt die JUnit-Methode nur einen "regulären" Typ zurück, während die AssertJ-Methode eine AbstractAssertUnterklasse zurückgibt ... die das Aneinanderreihen von Methoden wie oben ermöglicht (oder was auch immer die technischen Begriffe dafür sind ...).
Mike Nagetier
@weston Eigentlich habe ich gerade Ihre Technik in AssertJ 2.0.0 verwendet. Zweifellos keine Entschuldigung dafür, kein Upgrade durchzuführen, aber obwohl Sie es vielleicht gerne wissen würden.
Mike Nagetier
33

Um das gleiche Problem zu lösen, habe ich ein kleines Projekt eingerichtet: http://code.google.com/p/catch-exception/

Mit diesem kleinen Helfer würdest du schreiben

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

Dies ist weniger ausführlich als die ExpectedException-Regel von JUnit 4.7. Im Vergleich zu der von skaffman bereitgestellten Lösung können Sie angeben, in welcher Codezeile Sie die Ausnahme erwarten. Ich hoffe das hilft.

rwitzel
quelle
Ich habe auch darüber nachgedacht, aber letztendlich festgestellt, dass die wahre Stärke von ExpectedException darin besteht, dass Sie nicht nur die erwartete Ausnahme angeben können, sondern auch bestimmte Eigenschaften der Ausnahme wie die erwartete Ursache oder die erwartete Nachricht.
Jason Thompson
Ich vermute, dass diese Lösung einige der gleichen Nachteile hat wie Mocks? Wenn zum Beispiel fooist finales fehl , weil Sie nicht Proxy foo?
Tom
Tom, wenn doStuff () Teil einer Schnittstelle ist, funktioniert der Proxy-Ansatz. Andernfalls schlägt dieser Ansatz fehl, Sie haben Recht.
Rwitzel
31

Update: JUnit5 hat eine Verbesserung für das Testen von Ausnahmen : assertThrows.

Das folgende Beispiel stammt aus: Junit 5 Benutzerhandbuch

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

Ursprüngliche Antwort mit JUnit 4.

Es gibt verschiedene Möglichkeiten, um zu testen, ob eine Ausnahme ausgelöst wird. Ich habe auch die folgenden Optionen in meinem Beitrag besprochen. Wie schreibe ich großartige Unit-Tests mit JUnit?

Stellen Sie den expectedParameter ein @Test(expected = FileNotFoundException.class).

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

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

}

Testen mit ExpectedExceptionRegel.

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

Weitere Informationen zu Ausnahmetests finden Sie im JUnit4-Wiki für Ausnahmetests und in bad.robot - Expecting Exceptions JUnit Rule .

Dilini Rajapaksha
quelle
22

Sie können dies auch tun:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}
John Mikic
quelle
12
In JUnit-Tests ist es besser, diese Assert.fail()nicht zu verwenden assert, nur für den Fall, dass Ihre Tests in einer Umgebung ausgeführt werden, in der Zusicherungen nicht aktiviert sind.
NamshubWriter
14

IMHO ist der beste Weg, um in JUnit nach Ausnahmen zu suchen, das Muster 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()); 
}

Das assertTruekönnte für einige Leute ein bisschen stark sein, also assertThat(e.getMessage(), containsString("the message");könnte es vorzuziehen sein.

Alex Collins
quelle
13

Die flexibelste und eleganteste Antwort für Junit 4, die ich im Mkyong-Blog gefunden habe . Es hat die Flexibilität try/catch, die @RuleAnnotation zu verwenden. Ich mag diesen Ansatz, weil Sie bestimmte Attribute einer benutzerdefinierten Ausnahme lesen können.

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

    }

}
Dherik
quelle
12

Ich habe viele der Methoden hier ausprobiert, aber sie waren entweder kompliziert oder entsprachen nicht ganz meinen Anforderungen. Tatsächlich kann man eine Hilfsmethode ganz einfach schreiben:

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

Verwenden Sie es so:

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

Keine Abhängigkeiten: Keine Notwendigkeit für Mockito, keine Notwendigkeit für Powermock; und funktioniert gut mit Abschlussklassen.

Hugh Perkins
quelle
Interessant, passt aber nicht zu AAA (Arrange Act Assert), wo Sie den Act- und den Assert-Schritt in tatsächlich unterschiedlichen Schritten ausführen möchten.
Bln-Tom
1
@ bln-tom Technisch gesehen sind es zwei verschiedene Schritte, sie sind einfach nicht in dieser Reihenfolge. ; p
Trejkaz
10

Java 8-Lösung

Wenn Sie eine Lösung wünschen, die:

  • Verwendet Java 8 Lambdas
  • Hängt nicht von JUnit-Magie ab
  • Ermöglicht das Überprüfen mehrerer Ausnahmen innerhalb einer einzelnen Testmethode
  • Überprüft, ob eine Ausnahme von einem bestimmten Satz von Zeilen innerhalb Ihrer Testmethode anstelle einer unbekannten Zeile in der gesamten Testmethode ausgelöst wird
  • Gibt das tatsächlich ausgelöste Ausnahmeobjekt aus, damit Sie es weiter untersuchen können

Hier ist eine Utility-Funktion, die ich geschrieben habe:

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

( aus meinem Blog )

Verwenden Sie es wie folgt:

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

In meinem Fall erhalte ich immer RuntimeException von db, aber die Nachrichten unterscheiden sich. Und Ausnahmen müssen jeweils behandelt werden. So habe ich es getestet:

@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));
}
Macchiatow
quelle
1
vor der Zeile mit } catch (sollten Sie einfügenfail("no exception thrown");
Daniel Alder
6

Erstellen Sie einfach einen Matcher, der wie folgt ein- und ausgeschaltet werden kann:

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

Um es zu benutzen:

dann füge hinzu public ExpectedException exception = ExpectedException.none();:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();
Tor P.
quelle
6

In JUnit 4 oder höher können Sie die Ausnahmen wie folgt testen

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


Dies bietet viele Funktionen, mit denen unsere JUnit-Tests verbessert werden können.
Wenn Sie das folgende Beispiel sehen, teste ich 3 Dinge für die Ausnahme.

  1. Die Art der ausgelösten Ausnahme
  2. Die Ausnahme Nachricht
  3. Die Ursache der Ausnahme


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

}
JoBⅈN
quelle
6

Wir können einen Assertion Fail nach der Methode verwenden, die eine Ausnahme zurückgeben muss:

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());
}
Shessuky
quelle
3
Die zweite catchwürde die Stapelverfolgung verschlucken, wenn eine andere Ausnahme ausgelöst wird und nützliche Informationen verloren gehen
NamshubWriter
5

Stellen Sie zusätzlich zu den Aussagen von NamShubWriter Folgendes sicher:

  • Die ExpectedException-Instanz ist öffentlich ( verwandte Frage )
  • Die ExpectedException wird beispielsweise in der @ Before-Methode nicht instanziiert. In diesem Beitrag werden alle Feinheiten der Ausführungsreihenfolge von JUnit klar erläutert.

Mach das nicht :

@Rule    
public ExpectedException expectedException;

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

Schließlich zeigt dieser Blog-Beitrag deutlich, wie eine bestimmte Ausnahme ausgelöst werden kann.

Bruce Wayne
quelle
4

Ich empfehle die Bibliothek assertj-core, um Ausnahmen im Junit-Test zu behandeln

In Java 8 wie folgt:

//given

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

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

Die Junit4-Lösung mit Java8 besteht darin, diese Funktion zu verwenden:

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

Verwendung ist dann:

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

Beachten Sie, dass die einzige Einschränkung darin besteht, eine finalObjektreferenz im Lambda-Ausdruck zu verwenden. Diese Lösung ermöglicht es, Testzusicherungen fortzusetzen, anstatt zu erwarten, dass sie auf Methodenebene mit der @Test(expected = IndexOutOfBoundsException.class)Lösung verwendet werden können.

Donatello
quelle
1

Nehmen wir zum Beispiel, Sie möchten Junit für das unten genannte Codefragment schreiben

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

Der obige Code dient zum Testen auf eine unbekannte Ausnahme, die auftreten kann, und der folgende dient zum Aktivieren einer Ausnahme mit einer benutzerdefinierten Nachricht.

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

               }

    }

Hier ist eine andere Möglichkeit, um zu überprüfen, ob die Methode die richtige Ausnahme ausgelöst hat oder nicht.

MangduYogii
quelle
1

Das JUnit-Framework verfügt über folgende assertThrows()Methode:

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

Mit Java 8 können Sie eine Methode erstellen, die einen Code zum Überprüfen und die erwartete Ausnahme als Parameter verwendet:

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

und dann in Ihrem Test:

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

Leistungen:

  • sich nicht auf eine Bibliothek verlassen
  • Lokalisierte Prüfung - präziser und ermöglicht bei Bedarf mehrere Aussagen wie diese innerhalb eines Tests
  • Einfach zu verwenden
fahrenx
quelle