Come posso affermare il mio messaggio di eccezione con l'annotazione JUnit Test?


315

Ho scritto alcuni test di JUnit con @Testannotazioni. Se il mio metodo di prova genera un'eccezione controllata e se voglio affermare il messaggio insieme all'eccezione, c'è un modo per farlo con l' @Testannotazione JUnit ? AFAIK, JUnit 4.7 non fornisce questa funzione ma ci sono versioni future? So che in .NET puoi affermare il messaggio e la classe di eccezione. Alla ricerca di funzionalità simili nel mondo Java.

Questo è quello che voglio:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}

1
Ora che ci penso un po 'di più ... Sei sicuro che sia una buona idea affermare il messaggio? La tua domanda mi ha fatto scavare un po 'nel codice sorgente di junit e sembra che avrebbero potuto facilmente aggiungere questa funzione. Il fatto che hanno fatto non , mi fa pensare che non potrebbe essere considerato una buona pratica. Perché è importante nel tuo progetto affermare il messaggio?
c_maker,

10
buona domanda. Dire che un metodo contenente 15 righe di codice genera la stessa eccezione da 2 posizioni diverse. I miei casi di test devono far valere non solo la classe di eccezione ma anche il messaggio in essa contenuto. In un mondo ideale, qualsiasi comportamento anomalo dovrebbe avere una sua eccezione. In tal caso, la mia domanda non sorgerebbe mai, ma le applicazioni di produzione non hanno la loro unica eccezione personalizzata per ogni comportamento anomalo.
Cshah,

Come nota @expectedExceptionMessagea margine : c'è un'annotazione in PHPUnit.
bancer

Risposte:


535

È possibile utilizzare l' @Ruleannotazione con ExpectedException, in questo modo:

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

Nota che l'esempio nei ExpectedExceptiondocumenti è (attualmente) sbagliato - non esiste un costruttore pubblico, quindi devi usarlo ExpectedException.none().


1
Nota: per me quando è expectMessagestato specificato come stringa vuota, il confronto per il messaggio non è stato eseguito
redDevil

1
Utile per me. Grazie. Il metodo di test dovrebbe avere throws RuntimeExceptiondopo l'aggiunta del codice che genera un'eccezione. Non prenderlo ...
Bumbolt

5
Personalmente non vorrei usarlo poiché la creazione di campi ai fini di un piccolo sottoinsieme di metodi è una cattiva pratica. Non una critica alla risposta, ma al design di JUnit. L'ipotetica soluzione del PO sarebbe molto meglio se esistesse.
Sridhar Sarnobat,

2
@redDevil: expectedMessage verifica se il messaggio di errore "contiene" la stringa specificata in questa funzione (come una sottostringa del messaggio di errore)
tuan.dinh,

3
awaMessage con il parametro string esegue un controllo String.contains, per una corrispondenza esatta del messaggio di eccezione utilizzare hamcrest matcherfailure.expectMessage(CoreMatchers.equalTo(...))
Sivabalan,

42

Mi piace la @Rulerisposta. Tuttavia, se per qualche motivo non vuoi usare le regole. C'è una terza opzione.

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

32

Devi usare @Test(expected=SomeException.class)? Quando dobbiamo affermare il messaggio effettivo dell'eccezione, questo è ciò che facciamo.

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

6
Sono consapevole di scrivere un blocco catch e di usare assert all'interno di esso, ma per una migliore leggibilità del codice voglio fare con le annotazioni.
Cshah,

Inoltre non riceverai un messaggio così piacevole come quando lo fai nel modo "giusto".
NeplatnyUdaj,

15
Il problema con la versione try / catch, ora fornita da JUnit @Test(expected=...)e ExpectedException, è che ho visto in numerose occasioni qualcuno che si dimenticava di effettuare la chiamata alla fail()fine del tryblocco . Se non colto dalla revisione del codice, il test potrebbe essere falso positivo e passare sempre.
William Price,

Ecco perché non mi piacciono tutte queste cose dichiarative. Rende difficile l'accesso a ciò che desideri.
Sridhar Sarnobat,

30

In JUnit 4.13 puoi fare:

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

Questo funziona anche in JUnit 5 ma con diverse importazioni:

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

...

Ti piace questa soluzione. Dovrebbe passare a JUnit 5.
WesternGun

Gaaaaaaaaa. 4.13 ancora in versione beta ad oggi (autunno, 2019)? mvnrepository.com/artifact/junit/junit
granadaCoder

v4.13 non è più in stato beta (rilascio a gennaio 2020)
Simon

11

In realtà, l'uso migliore è con try / catch. Perché? Perché puoi controllare il luogo in cui ti aspetti l'eccezione.

Considera questo esempio:

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

Cosa succede se un giorno il codice viene modificato e la preparazione del test genererà una RuntimeException? In tal caso il test effettivo non viene nemmeno testato e anche se non genera alcuna eccezione, il test passerà.

Ecco perché è molto meglio usare try / catch che fare affidamento sull'annotazione.


Purtroppo, questa è anche la mia risposta.
Sridhar Sarnobat,

2
Le preoccupazioni relative alle modifiche al codice vengono alleviate con casi di test piccoli, specifici per permutazione. A volte è inevitabile e dobbiamo fare affidamento sul metodo catch / try, ma se ciò accade frequentemente, allora è probabile che abbiamo bisogno di rivedere il modo in cui scriviamo le nostre funzioni del test case.
luis.espinal,

Questo è un problema con il tuo test e / o codice. NON ti aspetti una RuntimeException generale, ti aspetti un'eccezione specifica o almeno un messaggio specifico.
DennisK,

Ho usato RuntimeExceptioncome esempio, sostituire questa eccezione con qualsiasi altra eccezione.
Krzysztof Cislo,

8

Raystorm aveva una buona risposta. Non sono neanche un grande fan delle Regole. Faccio qualcosa di simile, tranne per il fatto che creo la seguente classe di utilità per aiutare la leggibilità e l'usabilità, che è uno dei maggiori vantaggi delle annotazioni in primo luogo.

Aggiungi questa classe di utilità:

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

}

Quindi per il mio test unitario, tutto ciò di cui ho bisogno è questo codice:

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

2

Se si utilizza @Rule, il set di eccezioni viene applicato a tutti i metodi di test nella classe Test.


2
Utilizzando la risposta Jesse Merriman, l'eccezione viene verificata solo nei metodi di test che chiamano prevedonoEx.expect () e expectedEx.expectMessage (). Gli altri metodi utilizzeranno la definizione expectedEx = ExpectedException.none (), ovvero nessuna eccezione prevista.
Egl,

2

Non mi è mai piaciuto il modo di affermare eccezioni con Junit. Se uso "previsto" nell'annotazione, dal mio punto di vista sembra che stiamo violando il modello "dato, quando, poi" perché "allora" è posizionato nella parte superiore della definizione del test.

Inoltre, se utilizziamo "@Rule", abbiamo a che fare con così tanto codice del boilerplate. Quindi, se puoi installare nuove librerie per i tuoi test, suggerirei di dare un'occhiata ad AssertJ (quella libreria ora viene fornita con SpringBoot)

Quindi un test che non viola i principi "dato / quando / allora" e viene fatto usando AssertJ per verificare:

1 - L'eccezione è ciò che ci aspettiamo. 2 - Ha anche un messaggio previsto

Assomiglierà a questo:

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

1

Mi piace la risposta di user64141 ma ho scoperto che potrebbe essere più generalizzata. Ecco la mia opinione:

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

}

Si noti che lasciando l'istruzione "fail" all'interno del blocco try viene catturata la relativa eccezione di asserzione; l'utilizzo di return all'interno dell'istruzione catch impedisce questo.


0

Importa la libreria di eccezioni catch e usala. È molto più pulito della ExpectedExceptionregola o di un try-catch.

Esempio dai loro documenti:

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

-2
@Test (expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "This is not allowed")
public void testInvalidValidation() throws Exception{
     //test code
}

Qualcuno può aiutarmi a capire perché questa risposta è -1
aasha

La domanda è, Junitma la tua risposta sta dandoTestNG
Huazhe Yin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.