Jak potwierdzić komunikat o wyjątku za pomocą adnotacji testu JUnit?

314

Napisałem kilka testów JUnit z @Testadnotacjami. Jeśli moja metoda testowa zgłasza sprawdzony wyjątek i jeśli chcę potwierdzić komunikat wraz z wyjątkiem, czy istnieje sposób, aby to zrobić za pomocą @Testadnotacji JUnit ? AFAIK, JUnit 4.7 nie udostępnia tej funkcji, ale czy są dostępne w przyszłych wersjach? Wiem, że w .NET możesz potwierdzić komunikat i klasę wyjątku. Szukasz podobnej funkcji w świecie Java.

To jest to czego chcę:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}
Cshah
źródło
1
Teraz, gdy o tym myślę trochę więcej ... Czy jesteś pewien, że dobrze jest przekazać wiadomość? Twoje pytanie sprawiło, że trochę zagłębiłem się w kod źródłowy junita i wydaje się, że mogliby z łatwością dodać tę funkcję. Fakt, że oni nie sprawia mi myśleć, że nie można uznać za dobrą praktykę. Dlaczego w Twoim projekcie ważne jest potwierdzenie wiadomości?
c_maker
10
dobre pytanie. Powiedz, że metoda zawierająca 15 wierszy kodu zgłasza ten sam wyjątek z 2 różnych miejsc. Moje przypadki testowe muszą zapewniać nie tylko klasę wyjątków, ale także zawartą w niej wiadomość. W idealnym świecie każde nienormalne zachowanie powinno mieć własny wyjątek. Gdyby tak było, moje pytanie nigdy by się nie pojawiło, ale aplikacje produkcyjne nie mają swojego wyjątkowego niestandardowego wyjątku dla każdego nienormalnego zachowania.
Cshah
Na marginesie - @expectedExceptionMessagew PHPUnit znajduje się adnotacja.
bancer

Odpowiedzi:

535

Możesz użyć @Ruleadnotacji w ExpectedExceptionnastępujący sposób:

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

Zauważ, że przykład w ExpectedExceptiondokumentacji jest (obecnie) zły - nie ma publicznego konstruktora, więc musisz użyć ExpectedException.none().

Jesse Merriman
źródło
1
Uwaga: Dla mnie, gdy expectMessageokreślono jako pusty ciąg, porównanie wiadomości nie zostało wykonane
redDevil
1
Przydatne dla mnie. Dzięki. Metoda testowa powinna mieć throws RuntimeExceptionpo dodaniu kodu, który zgłasza wyjątek. Nie łapcie tego ...
Bumbolt
5
Osobiście nie chciałbym tego używać, ponieważ tworzenie pól na potrzeby niewielkiego zestawu metod jest złą praktyką. Nie krytyka odpowiedzi, ale projekt JUnita. Hipotetyczne rozwiązanie OP byłoby znacznie lepsze, gdyby istniało.
Sridhar Sarnobat
2
@redDevil: Oczekiwany komunikat sprawdza, czy komunikat o błędzie „zawiera” ciąg znaków określony w tej funkcji (jak podciąg komunikatu o błędzie)
tuan.dinh
3
expectMessage z parametrem string wykonuje String. zawiera sprawdzanie, w celu dokładnego dopasowania komunikatu wyjątku użyj dopasowywania failure.expectMessage(CoreMatchers.equalTo(...))
hamcrest
41

Podoba mi się @Ruleodpowiedź. Jeśli jednak z jakiegoś powodu nie chcesz używać reguł. Istnieje trzecia opcja.

@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
źródło
32

Czy musisz użyć @Test(expected=SomeException.class)? Kiedy musimy potwierdzić faktyczny komunikat wyjątku, robimy to.

@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
źródło
6
Wiem, że piszę blok catch i używam w tym celu funkcji assert, ale dla lepszej czytelności kodu chcę zrobić z adnotacjami.
Cshah
Nie dostaniesz też tak miłej wiadomości, jak przy „właściwym” sposobie.
NeplatnyUdaj
15
Problem z wersją try / catch, którą zapewnia JUnit @Test(expected=...)i ExpectedExceptionpolega na tym, że wielokrotnie widziałem kogoś, kto zapomniał zadzwonić fail()na końcu trybloku . Jeśli nie zostanie przechwycony przez przegląd kodu, test może być fałszywie dodatni i zawsze pozytywny.
William Price
Dlatego nie lubię tych wszystkich deklaratywnych rzeczy. Utrudnia dostęp do tego, co chcesz.
Sridhar Sarnobat
30

W JUnit 4.13 możesz:

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

Działa to również w JUnit 5, ale z różnymi importami:

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

...
Johan Boberg
źródło
Polub to rozwiązanie. Powinien przenieść się do JUnit 5.
WesternGun
Gaaaaaaaaa. 4.13 na dzień dzisiejszy w wersji beta (jesień, 2019)? mvnrepository.com/artifact/junit/junit
granadaCoder
Wersja 4.13 nie jest już w fazie beta (wydanie w styczniu 2020 r.)
Simon
11

W rzeczywistości najlepsze użycie to try / catch. Dlaczego? Ponieważ możesz kontrolować miejsce, w którym oczekujesz wyjątku.

Rozważ ten przykład:

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

Co się stanie, jeśli pewnego dnia kod zostanie zmodyfikowany, a przygotowanie do testu zgłosi wyjątek RuntimeException? W takim przypadku rzeczywisty test nie jest nawet testowany, a nawet jeśli nie zgłosi żadnego wyjątku, test zostanie zaliczony.

Dlatego o wiele lepiej jest użyć try / catch niż polegać na adnotacji.

Krzysztof Cisło
źródło
Niestety, to także moja odpowiedź.
Sridhar Sarnobat
2
Obawy dotyczące zmian kodu są rozwiązywane przez małe przypadki testowe specyficzne dla permutacji. Czasami jest to nieuniknione i musimy polegać na metodzie catch / try, ale jeśli zdarza się to często, istnieje szansa, że ​​musimy zrewidować sposób pisania naszych funkcji przypadków testowych.
luis.espinal
To jest problem z twoim testem i / lub kodem. NIE oczekujesz ogólnego wyjątku RuntimeException, oczekujesz konkretnego wyjątku lub przynajmniej określonego komunikatu.
DennisK,
Użyłem RuntimeExceptionjako przykładu, zastąp ten wyjątek dowolnym innym wyjątkiem.
Krzysztof Cislo,
8

Raystorm miał dobrą odpowiedź. Nie jestem też wielkim fanem zasad. Robię coś podobnego, z wyjątkiem tego, że tworzę następującą klasę użyteczności, aby zwiększyć czytelność i użyteczność, co jest jednym z największych plusów adnotacji.

Dodaj tę klasę narzędzi:

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

}

Następnie do mojego testu jednostkowego potrzebuję tylko tego kodu:

@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"
}
użytkownik64141
źródło
2

Jeśli używana jest reguła @, zestaw wyjątków jest stosowany do wszystkich metod testowych w klasie Test.

Jyothi
źródło
2
Za pomocą odpowiedzi Jessego Merrimana wyjątek jest sprawdzany tylko w metodach testowych, które wywołują oczekiwaneExExtect () i oczekiwaneExExtectMessage (). Inne metody wykorzystają definicję oczekiwanyExEx = ExpectedException.none (), to znaczy nie oczekuje się wyjątku.
Egl
2

Nigdy nie podobało mi się, że w Junit można znaleźć wyjątki. Jeśli użyję słowa „oczekiwany” w adnotacji, z mojego punktu widzenia wydaje się, że naruszamy „dany, kiedy, to” wzorzec, ponieważ „wtedy” znajduje się na górze definicji testu.

Ponadto, jeśli użyjemy „@Rule”, będziemy musieli poradzić sobie z tak dużą liczbą kodów źródłowych. Więc jeśli możesz zainstalować nowe biblioteki do swoich testów, sugeruję zajrzeć do AssertJ (ta biblioteka jest teraz dostarczana z SpringBoot)

Następnie test, który nie narusza zasad „dany / kiedy / wtedy” i jest wykonywany przy użyciu AssertJ w celu weryfikacji:

1 - Spodziewamy się wyjątku. 2 - Ma również oczekiwany komunikat

Będzie wyglądać tak:

 @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
źródło
1

Podoba mi się odpowiedź user64141, ale stwierdziłem, że można ją bardziej uogólnić. Oto moje zdanie:

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

}

Zauważ, że pozostawienie instrukcji „fail” w bloku try powoduje przechwycenie powiązanego wyjątku asercji; zapobiega temu przez użycie return w instrukcji catch.

Addison Crump
źródło
0

Zaimportuj bibliotekę wyjątków przechwytywania i użyj jej. Jest znacznie czystszy niż ExpectedExceptionreguła lub try-catch.

Przykład z ich dokumentów:

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
źródło
-2
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}
aasha
źródło
Czy ktoś może mi pomóc zrozumieć, dlaczego ta odpowiedź to -1
aasha
Pytanie Junitnasuwa się, ale TestNG
twoja