Wie kann ich meine Ausnahmemeldung mit der Annotation JUnit Test bestätigen?

315

Ich habe einige JUnit-Tests mit @TestAnmerkungen geschrieben. Wenn meine Testmethode eine aktivierte Ausnahme auslöst und ich die Nachricht zusammen mit der Ausnahme bestätigen möchte, gibt es eine Möglichkeit, dies mit der JUnit- @TestAnnotation zu tun ? AFAIK, JUnit 4.7 bietet diese Funktion nicht, aber werden sie in zukünftigen Versionen bereitgestellt? Ich weiß, dass Sie in .NET die Nachricht und die Ausnahmeklasse bestätigen können. Auf der Suche nach ähnlichen Funktionen in der Java-Welt.

Das ist was ich will:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}
Cshah
quelle
1
Jetzt, wo ich ein bisschen mehr darüber nachdenke ... Sind Sie sicher, dass es eine gute Idee ist, die Botschaft zu behaupten? Ihre Frage hat mich dazu gebracht, mich ein wenig mit dem Junit-Quellcode zu beschäftigen, und es scheint, dass sie diese Funktion leicht hätten hinzufügen können. Die Tatsache , dass sie es taten nicht , macht ich denke , es könnte nicht eine gute Praxis in Betracht gezogen werden. Warum ist es in Ihrem Projekt wichtig, die Botschaft zu bestätigen?
c_maker
10
Gute Frage. Sagen Sie, dass eine Methode mit 15 Codezeilen dieselbe Ausnahme von 2 verschiedenen Stellen auslöst. Meine Testfälle müssen nicht nur die Ausnahmeklasse, sondern auch die darin enthaltene Nachricht bestätigen. In einer idealen Welt sollte jedes abnormale Verhalten seine eigene Ausnahme haben. Wenn dies der Fall gewesen wäre, würde meine Frage niemals auftauchen, aber Produktionsanwendungen haben nicht für jedes abnormale Verhalten eine eigene benutzerdefinierte Ausnahme.
Cshah
Als Randnotiz - es gibt @expectedExceptionMessageAnmerkungen in PHPUnit.
Bancer

Antworten:

535

Sie können die @RuleAnmerkung folgendermaßen verwenden ExpectedException:

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

Beachten Sie, dass das Beispiel in den ExpectedExceptionDokumenten (derzeit) falsch ist - es gibt keinen öffentlichen Konstruktor, daher müssen Sie verwenden ExpectedException.none().

Jesse Merriman
quelle
1
Hinweis: Für mich, als die expectMessageals leere Zeichenfolge angegeben wurde, wurde der Vergleich für die Nachricht nicht durchgeführt
redDevil
1
Nützlich für mich. Vielen Dank. Die Testmethode sollte throws RuntimeExceptionnach dem Hinzufügen von Code eine Ausnahme auslösen . Fangen Sie es nicht ...
Bumbolt
5
Ich persönlich würde dies nicht verwenden wollen, da das Erstellen von Feldern für den Zweck einer kleinen Teilmenge von Methoden eine schlechte Praxis ist. Keine Kritik an der Antwort, sondern am Design von JUnit. Die hypothetische Lösung des OP wäre viel besser, wenn sie existieren würde.
Sridhar Sarnobat
2
@redDevil: Die ExpectedMessage prüft, ob die Fehlermeldung die in dieser Funktion angegebene Zeichenfolge "enthält" (wie eine Teilzeichenfolge der Fehlermeldung)
tuan.dinh
3
ExpectMessage mit String-Parameter führt eine String.contains-Überprüfung durch, um die genaue Übereinstimmung der Ausnahmemeldung zu ermitteln. Verwenden Sie den Hamcrest-Matcherfailure.expectMessage(CoreMatchers.equalTo(...))
Sivabalan,
42

Ich mag die @RuleAntwort. Wenn Sie jedoch aus irgendeinem Grund keine Regeln verwenden möchten. Es gibt eine dritte Option.

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }
Raystorm
quelle
32

Müssen Sie verwenden @Test(expected=SomeException.class)? Wenn wir die eigentliche Meldung der Ausnahme bestätigen müssen, tun wir dies.

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}
c_maker
quelle
6
Ich bin mir bewusst, dass ich einen catch-Block schreibe und darin assert verwende, aber für eine bessere Lesbarkeit des Codes möchte ich mit Anmerkungen arbeiten.
Cshah
Außerdem erhalten Sie keine so nette Nachricht, wie wenn Sie es "richtig" machen.
NeplatnyUdaj
15
Das Problem mit der Try / Catch - Version, jetzt , dass JUnit bietet @Test(expected=...)und ExpectedException, ist , dass ich bei zahlreichen Gelegenheiten jemand gesehen haben vergessen , den Anruf zu setzen , um fail()am Ende des tryBlockes . Wenn Sie nicht von der Codeüberprüfung erfasst werden, ist Ihr Test möglicherweise falsch positiv und besteht immer.
William Price
Deshalb mag ich all diese deklarativen Sachen nicht. Es macht es schwierig, auf das zuzugreifen, was Sie wollen.
Sridhar Sarnobat
30

In JUnit 4.13 können Sie Folgendes tun:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

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

  assertEquals("a message", exception.getMessage());
}

Dies funktioniert auch in JUnit 5, jedoch mit unterschiedlichen Importen:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

...
Johan Boberg
quelle
Wie diese Lösung. Sollte zu JUnit 5
wechseln
Gaaaaaaaaa. 4.13 noch in der Beta ab heute (Herbst 2019)? mvnrepository.com/artifact/junit/junit
granadaCoder
v4.13 befindet sich nicht mehr im Beta-Status (Veröffentlichung im Januar 2020)
Simon
11

Eigentlich ist die beste Verwendung mit try / catch. Warum? Weil Sie den Ort steuern können, an dem Sie die Ausnahme erwarten.

Betrachten Sie dieses Beispiel:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

Was ist, wenn eines Tages der Code geändert wird und die Testvorbereitung eine RuntimeException auslöst? In diesem Fall wird der tatsächliche Test nicht einmal getestet, und selbst wenn keine Ausnahme ausgelöst wird, besteht der Test.

Deshalb ist es viel besser, try / catch zu verwenden, als sich auf die Annotation zu verlassen.

Krzysztof Cislo
quelle
Leider ist dies auch meine Antwort.
Sridhar Sarnobat
2
Die Bedenken hinsichtlich Codeänderungen werden durch kleine permutationsspezifische Testfälle ausgeräumt. Manchmal ist es unvermeidlich und wir müssen uns auf die catch / try-Methode verlassen, aber wenn dies häufig vorkommt, müssen wir wahrscheinlich die Art und Weise, wie wir unsere Testfallfunktionen schreiben, überarbeiten.
Luis.espinal
Das ist ein Problem mit Ihrem Test und / oder Code. Sie erwarten KEINE allgemeine RuntimeException, Sie erwarten eine bestimmte Ausnahme oder zumindest eine bestimmte Nachricht.
DennisK
Ich habe RuntimeExceptionals Beispiel diese Ausnahme durch eine andere Ausnahme ersetzt.
Krzysztof Cislo
8

Raystorm hatte eine gute Antwort. Ich bin auch kein großer Fan von Regeln. Ich mache etwas Ähnliches, außer dass ich die folgende Utility-Klasse erstelle, um die Lesbarkeit und Benutzerfreundlichkeit zu verbessern. Dies ist eines der großen Pluspunkte von Anmerkungen.

Fügen Sie diese Dienstprogrammklasse hinzu:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

Dann brauche ich für meinen Unit-Test nur diesen Code:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}
user64141
quelle
2

Bei Verwendung von @Rule wird der Ausnahmesatz auf alle Testmethoden in der Testklasse angewendet.

Jyothi
quelle
2
Bei Verwendung der Antwort von Jesse Merriman wird die Ausnahme nur in Testmethoden überprüft, die auf "ExpectEx.expect ()" und "ExpectedEx.expectMessage ()" aufgerufen werden. Die anderen Methoden verwenden die Definition "ExpectedEx = ExpectedException.none ()", dh es wird keine Ausnahme erwartet.
Egl
2

Ich mochte es nie, mit Junit Ausnahmen geltend zu machen. Wenn ich das "Erwartete" in der Anmerkung verwende, scheinen wir aus meiner Sicht das Muster "Gegeben, Wann, Dann" zu verletzen, da das "Dann" oben in der Testdefinition steht.

Wenn wir "@Rule" verwenden, müssen wir uns auch mit so viel Boilerplate-Code auseinandersetzen. Wenn Sie also neue Bibliotheken für Ihre Tests installieren können, würde ich empfehlen, einen Blick auf AssertJ zu werfen (diese Bibliothek wird jetzt mit SpringBoot geliefert).

Dann ein Test, der nicht gegen die "gegebenen / wann / dann" -Prinzipien verstößt, und der mit AssertJ durchgeführt wird, um Folgendes zu überprüfen:

1 - Die Ausnahme ist das, was wir erwarten. 2 - Es hat auch eine erwartete Nachricht

Wird so aussehen:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}
Geeksusma
quelle
1

Ich mag die Antwort von user64141, habe aber festgestellt, dass sie allgemeiner sein könnte. Hier ist meine Einstellung:

public abstract class ExpectedThrowableAsserter implements Runnable {

    private final Class<? extends Throwable> throwableClass;
    private final String expectedExceptionMessage;

    protected ExpectedThrowableAsserter(Class<? extends Throwable> throwableClass, String expectedExceptionMessage) {
        this.throwableClass = throwableClass;
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run() {
        try {
            expectException();
        } catch (Throwable e) {
            assertTrue(String.format("Caught unexpected %s", e.getClass().getSimpleName()), throwableClass.isInstance(e));
            assertEquals(String.format("%s caught, but unexpected message", throwableClass.getSimpleName()), expectedExceptionMessage, e.getMessage());
            return;
        }
        fail(String.format("Expected %s, but no exception was thrown.", throwableClass.getSimpleName()));
    }

    protected abstract void expectException();

}

Beachten Sie, dass das Belassen der Anweisung "fail" im try-Block dazu führt, dass die zugehörige Assertion-Ausnahme abgefangen wird. Die Verwendung von return innerhalb der catch-Anweisung verhindert dies.

Addison Crump
quelle
0

Importieren Sie die Catch-Exception- Bibliothek und verwenden Sie diese. Es ist viel sauberer als die ExpectedExceptionRegel oder ein try-catch.

Beispiel aus ihren Dokumenten:

import static com.googlecode.catchexception.CatchException.*;
import static com.googlecode.catchexception.apis.CatchExceptionHamcrestMatchers.*;

// given: an empty list
List myList = new ArrayList();

// when: we try to get the first element of the list
catchException(myList).get(1);

// then: we expect an IndexOutOfBoundsException with message "Index: 1, Size: 0"
assertThat(caughtException(),
  allOf(
    instanceOf(IndexOutOfBoundsException.class),
    hasMessage("Index: 1, Size: 0"),
    hasNoCause()
  )
);
whistling_marmot
quelle
-2
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}
Aasha
quelle
Kann mir jemand helfen zu verstehen, warum diese Antwort ein -1
aasha
Die Frage ist gefragt, Junitaber TestNG
Ihre