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


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

Ich kann zwar so etwas machen:

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

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

  assertTrue(thrown);
}

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


1621
2017-10-01 06:56


Ursprung


Antworten:


JUnit 4 unterstützt dies:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

Referenz: https://junit.org/junit4/faq.html#atests_7


1942
2017-10-01 07:12



Bearbeiten Jetzt, wo JUnit5 veröffentlicht wurde, wäre die beste Option zu verwenden Assertions.assertThrows() (sehen meine andere Antwort).

Wenn Sie nicht zu JUnit 5 migriert haben, aber JUnit 4.7 verwenden können, können Sie den Befehl verwenden ExpectedException Regel:

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

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

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

Das ist viel besser als @Test(expected=IndexOutOfBoundsException.class) weil der Test fehlschlägt, wenn IndexOutOfBoundsException wird vorher geworfen foo.doStuff()

Sehen Dieser Artikel für Details


1165
2018-05-29 17:16



Seien Sie vorsichtig bei der Verwendung der erwarteten Ausnahme, da nur die Methode warf diese Ausnahme, nicht ein bestimmte Codezeile In der Prüfung.

Ich neige dazu, dies zum Testen der Parametervalidierung zu verwenden, da solche Methoden normalerweise sehr einfach sind, aber komplexere Tests besser mit

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

Beurteile das Urteil.


408
2017-10-01 09:31



Wie bereits erwähnt, gibt es viele Möglichkeiten, mit Ausnahmen in JUnit umzugehen. Aber mit Java 8 gibt es noch einen anderen: Lambda Expressions verwenden. Mit Lambda Expressions erreichen wir eine Syntax wie diese:

@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 akzeptiert diese Schnittstelle erwartet und bereit sein, eine Ausnahme zu behandeln.

Dies ist eine relativ einfache, aber leistungsfähige Technik.

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

Der Quellcode kann hier gefunden werden: 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.


187
2017-07-07 22:35



In Junit gibt es vier Möglichkeiten, Ausnahmen zu testen.

  • Verwenden Sie für junit4.x das optionale Attribut 'expected' von Test annonation

    @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 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.
        }
    }
    
  • Schließlich können Sie für junit5.x auch assertThrows wie folgt verwenden

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

    • Der erste Weg wird verwendet, wenn Sie nur den Typ der Ausnahme testen möchten
    • Die anderen drei Möglichkeiten werden verwendet, wenn Sie die Ausnahmebedingungsnachricht weiter testen möchten
    • Wenn Sie Junit 3 verwenden, wird der 3. bevorzugt
    • Wenn Sie Junit 5 mögen, dann sollten Sie den 4. mögen
  • Für weitere Informationen können Sie lesen dieses Dokument und junit5 Bedienungsanleitung für Details.


84
2017-08-05 08:05



tl; dr

  • Pre-JDK8: Ich werde das alte gut empfehlen try-catch Block.

  • post-JDK8: Verwenden Sie AssertJ oder benutzerdefinierte Lambdas zum Bestätigen außergewöhnlich Verhalten.

Unabhängig von Junit 4 oder JUnit 5.

die lange Geschichte

Es ist möglich, sich selbst zu schreiben mach es selbst  try-catch Blockieren oder verwenden Sie die JUnit-Tools (@Test(expected = ...) oder der @Rule ExpectedException JUnit-Regelfunktion).

Aber diese Wege sind nicht so elegant und vermischen sich nicht gut Lesbarkeit weise mit anderen Werkzeugen.

  1. Das try-catch Blockieren Sie den Block um das getestete Verhalten und schreiben Sie die Assertion in den catch-Block, das ist in Ordnung, aber viele finden, dass dieser Stil den Lesefluss eines Tests unterbricht. Sie müssen auch ein schreiben Assert.fail am Ende von try blockieren, andernfalls kann der Test eine Seite der Behauptungen verpassen; PMD, Findbugs oder Sonar werden solche Probleme erkennen.

  2. Das @Test(expected = ...) Feature ist interessant, wie Sie weniger Code schreiben können und dann diesen Test schreiben ist weniger anfällig für Codierungsfehler. Aber Dieser Ansatz fehlt in einigen Bereichen.

    • Wenn der Test zusätzliche Dinge in der Exception überprüfen muss, wie die Ursache oder die Nachricht (gute Exception-Nachrichten sind wirklich wichtig, reicht möglicherweise ein genauer Ausnahme-Typ nicht aus).
    • Auch wenn die Erwartung in der Methode liegt, abhängig davon, wie der getestete Code geschrieben ist, kann der falsche Teil des Testcodes die Ausnahme auslösen, was zu einem falsch positiven Test führt, und ich bin mir nicht sicher, dass dies der Fall ist PMD, Findbugs oder Sonar gibt Hinweise auf solchen 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. Das ExpectedException Regel ist auch ein Versuch, die vorherigen Vorbehalte zu beheben, aber es fühlt sich ein wenig umständlich an zu verwenden, da es einen Erwartungsstil verwendet, EasyMock Benutzer kennen diesen Stil sehr gut. Es könnte für einige bequem sein, aber wenn Sie folgen Verhaltensorientierte Entwicklung (BDD) oder Anordnen Act Assert (AAA) Prinzipien der ExpectedException Regel passt nicht in diesen Schreibstil. Abgesehen davon kann es unter dem gleichen Problem leiden wie die @Test Art und Weise, je nachdem, wo du die Erwartungen stellst.

    @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 die erwartete Ausnahme wird vor der Testanweisung platziert. Sie unterbricht Ihren Lesefluss, wenn die Tests BDD oder AAA folgen.

    Sieh das auch Kommentar Ausgabe auf JUnit des Autors von ExpectedException.

Daher haben diese obigen Optionen alle Vorbehalte und sind eindeutig nicht gegen Codiererfehler gefeit.

  1. Es gibt ein Projekt, das mir bewusst wurde, nachdem ich diese Antwort erstellt habe, die vielversprechend aussieht Fang-Ausnahme.

    Wie die Beschreibung des Projekts sagt, ließ es einen Coder in einer fließenden Codezeile schreiben, die die Ausnahme abfängt und diese Ausnahme für spätere Behauptungen anbietet. Und Sie können jede Assertion-Bibliothek verwenden Hamcrest oder AssertJ.

    Ein schnelles Beispiel aus 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, ist der Code sehr einfach, Sie fangen die Ausnahme in einer bestimmten Zeile, die then API ist ein Alias, der AssertJ-APIs verwendet (ähnlich wie bei der Verwendung von assertThat(ex).hasNoCause()...). Irgendwann beruhte das Projekt auf FEST-Assert den Vorfahren von AssertJ. BEARBEITEN: Es scheint, dass das Projekt eine Unterstützung von Java 8 Lambdas braut.

    Derzeit hat diese Bibliothek zwei Mängel:

    • Zum Zeitpunkt des Schreibens ist es erwähnenswert zu sagen, dass diese Bibliothek auf Mockito 1.x basiert, da es eine Kopie des getesteten Objekts hinter der Szene erzeugt. Da Mockito immer noch nicht aktualisiert wird Diese Bibliothek kann nicht mit finalen Klassen oder finalen Methoden arbeiten. Und selbst wenn es in der aktuellen Version auf Mockito 2 basiert, müsste man einen globalen Mock Maker deklarieren (inline-mock-maker), etwas, das vielleicht nicht das ist, was du willst, da dieser Spießer andere Nachteile hat als der normale Spötter.

    • Es erfordert eine weitere Testabhängigkeit.

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

    Wenn Sie das Catch-Exception-Tool nicht verwenden möchten, empfehle ich Ihnen den alten guten Weg try-catch Blockieren, zumindest bis zum JDK7. Und für JDK 8-Benutzer möchten Sie möglicherweise AssertJ verwenden, da es mehr bietet, als nur Ausnahmen zu bestätigen.

  2. Mit dem JDK8 treten Lambdas in die Testszene ein und sie haben sich als interessante Möglichkeit erwiesen, außergewöhnliches Verhalten zu beweisen. AssertJ wurde aktualisiert, um eine nette fließende API zur Verfügung zu stellen, um außergewöhnliches Verhalten zu bestätigen.

    Und ein Probentest 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 fast vollständigen Neufassung von JUnit 5 wurden Behauptungen aufgestellt verbessert ein wenig, sie können sich als eine Out-of-the-Box-Möglichkeit, um eine Ausnahme richtig geltend zu machen interessant erweisen. Aber die Assertion-API ist immer noch ein bisschen arm, 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 du bemerkt hast assertEquals kommt immer noch zurück void, und als solche nicht erlaubt Verkettung Assertionen wie AssertJ.

    Auch wenn Sie sich erinnern, dass Name mit Matcher oder Assert, sei darauf vorbereitet, den gleichen Zusammenstoß zu erleiden Assertions.

Ich möchte das heute schließen (2017-03-03) AssertJ's Benutzerfreundlichkeit, erkennbare API, schnelle Entwicklungstempo und als de facto Die Testabhängigkeit ist die beste Lösung mit JDK8, unabhängig vom Testframework (JUnit oder nicht), frühere JDKs sollten sich stattdessen darauf verlassen try-catch blockiert, auch wenn sie sich klobig fühlen.

Diese Antwort wurde von kopiert eine andere Frage die nicht die gleiche Sichtbarkeit haben, bin ich derselbe Autor.


58
2017-12-07 14:19



BDD Stil Lösung: JUnit 4 + Ausnahme abfangen + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

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

}

Quellcode

Abhängigkeiten

eu.codearte.catch-exception:catch-exception:1.3.3

31
2017-11-15 19:17



Wie wäre es damit: Fangen Sie eine sehr allgemeine Ausnahme ab, stellen Sie sicher, dass sie aus dem catch-Block herauskommt und bestätigen Sie dann, dass die Klasse der Ausnahme das ist, was Sie erwarten. Diese Bestätigung wird fehlschlagen, wenn a) die Ausnahme vom falschen Typ ist (z. B. wenn Sie stattdessen einen Nullzeiger erhalten haben) und b) die Ausnahme niemals ausgelöst wurde.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

30
2017-10-01 07:03



Mit einem AssertJ Behauptung, 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 die erwartete Zeile im Test garantiert warf die Ausnahme und lässt Sie mehr Details über die Ausnahme, z. B. Nachricht, einfacher überprüfen:

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

Maven / Gradle Anweisungen hier.


30
2018-03-05 11:07



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 zur Lösung von skaffman können Sie angeben, in welcher Codezeile Sie die Ausnahme erwarten. Ich hoffe das hilft.


29
2017-10-28 09:26