Come si afferma che una determinata eccezione viene generata nei test di JUnit 4?


2001

Come posso usare JUnit4 in modo idiomatico per verificare che alcuni codici generino un'eccezione?

Mentre posso certamente fare qualcosa del genere:

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

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

  assertTrue(thrown);
}

Ricordo che esiste un'annotazione o un Assert.xyz o qualcosa di molto meno complicato e molto più nello spirito di JUnit per questo tipo di situazioni.


21
Il problema con qualsiasi altro approccio, ma questo è che inevitabilmente terminano il test una volta che l'eccezione è stata lanciata. D'altra parte, spesso voglio ancora chiamare org.mockito.Mockito.verifycon vari parametri per assicurarmi che accadano determinate cose (in modo tale che un servizio di logger sia stato chiamato con i parametri corretti) prima che venga generata l'eccezione.
ZeroOne,

5
Puoi vedere come eseguire il test delle eccezioni nella pagina wiki di JUnit github.com/junit-team/junit/wiki/Exception-testing
PhoneixS

6
@ZeroOne - Per questo avrei due diversi test, uno per l'eccezione e uno per verificare l'interazione con il tuo finto.
tddmonkey,

C'è un modo per farlo con JUnit 5, ho aggiornato la mia risposta di seguito.
Dilini Rajapaksha,

Risposte:


2363

Dipende dalla versione di JUnit e dalle asserzioni librerie che usi.

La risposta originale per JUnit <= 4.12era:

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

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

}

Sebbene la risposta https://stackoverflow.com/a/31826781/2986984 abbia più opzioni per JUnit <= 4.12.

Riferimento:


66
Questo pezzo di codice non funzionerà se ti aspetti un'eccezione solo da qualche parte nel tuo codice e non una coperta come questa.
Oh Chin Boon,

4
@skaffman Questo non funzionerebbe con org.junit.experimental.theories.Theory gestito da org.junit.experimental.theories.Theories
Artem Oboturov

74
Roy Osherove scoraggia questo tipo di test di eccezione in The Art of Unit Testing , poiché l'eccezione potrebbe trovarsi ovunque all'interno del test e non solo all'interno dell'unità sotto test.
Kevin Wittek,

21
Non sono d'accordo con @ Kiview / Roy Osherove. A mio avviso, i test dovrebbero riguardare il comportamento, non l'implementazione. Testando che un metodo specifico può generare un errore, si collegano i test direttamente all'implementazione. Direi che i test con il metodo mostrato sopra forniscono un test più prezioso. L'avvertenza che aggiungerei è che in questo caso testerei un'eccezione personalizzata, in modo da sapere che sto ricevendo l'eccezione che desidero davvero.
Nickbdyer,

6
Nessuno dei due. Voglio testare il comportamento della classe. Ciò che è importante è che se provo a recuperare qualcosa che non c'è, ottengo un'eccezione. Il fatto che la struttura dei dati ArrayListrisponda get()è irrilevante. Se in futuro avessi scelto di passare a un array primitivo, avrei dovuto modificare questa implementazione di test. La struttura dei dati deve essere nascosta, in modo che il test possa concentrarsi sul comportamento della classe .
Nickbdyer,

1317

Modifica: ora che JUnit 5 e JUnit 4.13 sono stati rilasciati, l'opzione migliore sarebbe quella di utilizzare Assertions.assertThrows() (per JUnit 5) e Assert.assertThrows()(per JUnit 4.13). Vedi la mia altra risposta per i dettagli.

Se non hai eseguito la migrazione a JUnit 5, ma puoi utilizzare JUnit 4.7, puoi utilizzare la ExpectedExceptionRegola:

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

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

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

Questo è molto meglio che @Test(expected=IndexOutOfBoundsException.class)perché il test fallirà se IndexOutOfBoundsExceptionviene lanciato primafoo.doStuff()

Vedi questo articolo per i dettagli


14
@skaffman - Se l'ho capito correttamente, sembra che l'eccezione.aspetti venga applicata solo all'interno di un test, non dell'intera classe.
bacar,

5
Se l'eccezione che prevediamo di essere generata è un'eccezione verificata, dovremmo aggiungere i tiri o provare a catturare o testare questa situazione in un altro modo?
Mohammad Jafar Mashhadi,

5
@MartinTrummer Nessun codice deve essere eseguito dopo foo.doStuff () poiché viene generata l'eccezione e si esce dal metodo. Avere il codice dopo un'eccezione prevista (ad eccezione della chiusura delle risorse in a finalmente) non è comunque utile poiché non dovrebbe mai essere eseguito se viene generata l'eccezione.
Jason Thompson,

9
Questo è l'approccio migliore. Ci sono due vantaggi qui, rispetto alla soluzione di skaffman. In primo luogo, la ExpectedExceptionclasse ha modi per abbinare il messaggio dell'eccezione, o anche scrivere il proprio matcher che dipende dalla classe di eccezione. In secondo luogo, puoi impostare le tue aspettative immediatamente prima della riga di codice che prevedi di generare l'eccezione, il che significa che il test fallirà se la riga di codice errata genera l'eccezione; mentre non c'è modo di farlo con la soluzione di skaffman.
Dawood ibn Kareem,

5
@MJafarMash se viene verificata l'eccezione che si prevede di generare, aggiungere tale eccezione alla clausola di lancio del metodo di test. Fai lo stesso ogni volta che stai testando un metodo che viene dichiarato per generare un'eccezione controllata, anche se l'eccezione non viene attivata nel caso di test specifico.
NamshubWriter,

472

Fai attenzione usando l'eccezione prevista, perché afferma solo che il metodo ha generato quell'eccezione, non una particolare riga di codice nel test.

Tendo a usarlo per testare la validazione dei parametri, perché tali metodi sono generalmente molto semplici, ma test più complessi potrebbero essere meglio serviti con:

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

Applica giudizio.


95
Forse sono vecchia scuola ma preferisco ancora questo. Mi dà anche un posto per testare l'eccezione stessa: a volte ho eccezioni con getter per determinati valori, o potrei semplicemente cercare un valore particolare nel messaggio (ad esempio, cercando "xyz" nel messaggio "codice non riconosciuto 'xyz' ").
Rodney Gitzel,

3
Penso che l'approccio di NamshubWriter ti dia il meglio di entrambi i mondi.
Eddie,

4
Usando ExpectedException puoi chiamare N exception.expect per metodo per testare come questo exception.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664

10
@ user1154664 In realtà, non puoi. Usando ExpectedException puoi solo testare che un metodo genera un'eccezione, perché quando viene chiamato quel metodo, il test interromperà l'esecuzione perché ha generato l'eccezione prevista!
NamshubWriter

2
La tua prima frase non è vera. Quando si utilizza ExpectedException, la cosa normale da fare è impostare l'aspettativa immediatamente prima della linea che si prevede di lanciare l'eccezione. In questo modo, se una riga precedente genera l'eccezione, non attiverà la regola e il test fallirà.
Dawood ibn Kareem,

213

Come già detto, ci sono molti modi per gestire le eccezioni in JUnit. Ma con Java 8 ce n'è un altro: usare Lambda Expressions. Con Lambda Expressions possiamo ottenere una sintassi come questa:

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

assertThrown accetta un'interfaccia funzionale, le cui istanze possono essere create con espressioni lambda, riferimenti a metodi o riferimenti a costruttori. assertTrown accettando che l'interfaccia si aspetti e sia pronto a gestire un'eccezione.

Questa è una tecnica relativamente semplice ma potente.

Dai un'occhiata a questo post sul blog che descrive questa tecnica: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

Il codice sorgente può essere trovato qui: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Divulgazione: sono l'autore del blog e del progetto.


2
Mi piace questa soluzione ma posso scaricarla da un repository Maven?
Selwyn,

@Airduster un'implementazione di questa idea che è disponibile su Maven è stefanbirkner.github.io/vallado
NamshubWriter

6
@Cristiano: una versione più semplice di questa API è prevista per JUnit 4.13. Vedi github.com/junit-team/junit/commit/…
NamshubWriter

@RafalBorowiec tecnicamente, new DummyService()::someMethodè un MethodHandle, ma questo approccio funziona ugualmente bene con le espressioni lambda.
Andy,

@NamshubWriter, sembra che JUnit 4.13 è stato abbandonato a favore di JUnit 5: stackoverflow.com/questions/156503/...
Vadzim

154

in junit, ci sono quattro modi per testare l'eccezione.

junit5.x

  • per junit5.x, è possibile utilizzare assertThrowscome segue

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

junit4.x

  • per junit4.x, utilizzare l'attributo 'previsto' opzionale di Annonazione test

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
  • per junit4.x, utilizzare la regola ExpectedException

    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();
        }
    }
  • puoi anche usare il classico modo di provare / catturare ampiamente usato in junit 3 framework

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

    • se ti piace junit 5, allora ti dovrebbe piacere il primo
    • il secondo modo viene utilizzato quando si desidera solo testare il tipo di eccezione
    • il primo e gli ultimi due vengono utilizzati quando si desidera un ulteriore messaggio di eccezione del test
    • se usi junit 3, è preferibile il quarto
  • per maggiori informazioni, puoi leggere questo documento e la guida utente di junit5 per i dettagli.


6
Per me questa è la risposta migliore, copre tutti i modi in modo molto chiaro, grazie! Personalmente continuo a usare la terza opzione anche con Junit4 per leggibilità, per evitare blocchi di cattura vuoti puoi anche catturare Gettabile e affermare il tipo di e
Nicolas Cornette il

È possibile utilizzare ExpectedException per aspettarsi un'eccezione controllata?
miuser

Tutto ciò che è è un accumulo delle prime tre risposte. IMO, questa risposta non avrebbe nemmeno dovuto essere pubblicata se non aggiungesse nulla di nuovo. Sto solo rispondendo (una domanda popolare) per il rappresentante. Abbastanza inutile.
Paul Samsotha,

certo, perché puoi passare qualsiasi tipo derivato dal Trowablemetodo ExpectedException.expect. per favore vedi la sua firma . @miuser
walsh,

116

tl; dr

  • post-JDK8: utilizzare AssertJ o lambda personalizzati per affermare un comportamento eccezionale .

  • pre-JDK8: consiglierò il vecchio bene try- catchblocco. ( Non dimenticare di aggiungere fail()un'asserzione prima del catchblocco )

Indipendentemente da Junit 4 o JUnit 5.

la lunga storia

È possibile scrivere da soli e farlo da soli try - catchbloccare o utilizzare gli strumenti JUnit ( @Test(expected = ...)o la @Rule ExpectedExceptionfunzione della regola JUnit).

Ma questi modi non sono così eleganti e non mescolano bene la leggibilità con altri strumenti. Inoltre, gli strumenti JUnit presentano alcune insidie.

  1. Il blocco try- catchdevi scrivere il blocco attorno al comportamento testato e scrivere l'asserzione nel blocco catch, che può andare bene, ma molti trovano che questo stile interrompe il flusso di lettura di un test. Inoltre, è necessario scrivere un Assert.failalla fine del tryblocco. In caso contrario, il test potrebbe non avere una parte delle asserzioni; PMD , findbugs o Sonar individueranno tali problemi.

  2. La @Test(expected = ...)funzionalità è interessante in quanto è possibile scrivere meno codice e quindi scrivere questo test è presumibilmente meno soggetto a errori di codifica. Ma questo approccio manca in alcune aree.

    • Se il test deve verificare ulteriori elementi sull'eccezione, come la causa o il messaggio (i messaggi di buone eccezioni sono davvero importanti, avere un tipo di eccezione preciso potrebbe non essere sufficiente).
    • Inoltre, poiché l'aspettativa viene inserita nel metodo, a seconda di come viene scritto il codice testato, la parte errata del codice test può generare l'eccezione, portando a test falsi positivi e non sono sicuro che PMD , findbugs o Sonar darà suggerimenti su tale codice.

      @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. La ExpectedExceptionregola è anche un tentativo di correggere le avvertenze precedenti, ma è un po 'scomodo da usare in quanto utilizza uno stile di aspettativa, gli utenti EasyMock conoscono molto bene questo stile. Potrebbe essere utile per alcuni, ma se segui i principi Behavior Driven Development (BDD) o Arrange Act Assert (AAA) la ExpectedExceptionregola non si adatta a quegli stili di scrittura. A parte ciò, potrebbe soffrire dello stesso problema del @Testmodo, a seconda di dove si pone l'aspettativa.

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

    Anche l'eccezione prevista viene posta prima dell'istruzione test, interrompe il flusso di lettura se i test seguono BDD o AAA.

    Inoltre, vedi questo problema di commento su JUnit dell'autore di ExpectedException. JUnit 4.13-beta-2 addirittura depreca questo meccanismo:

    Richiesta pull n. 1519 : Deprecate ExpectedException

    Il metodo Assert.assertThrows fornisce un modo migliore per verificare le eccezioni. Inoltre, l'uso di ExpectedException è soggetto a errori se utilizzato con altre regole come TestWatcher perché l'ordine delle regole è importante in quel caso.

Quindi queste opzioni sopra hanno tutto il loro carico di avvertimenti e chiaramente non sono immuni da errori del programmatore.

  1. C'è un progetto di cui sono venuto a conoscenza dopo aver creato questa risposta che sembra promettente, è un'eccezione .

    Come dice la descrizione del progetto, consente a un programmatore di scrivere in una fluente riga di codice che rileva l'eccezione e offre questa eccezione per quest'ultima affermazione. E puoi usare qualsiasi libreria di asserzioni come Hamcrest o AssertJ .

    Un rapido esempio tratto dalla home page:

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

    Come puoi vedere il codice è davvero semplice, si rileva l'eccezione su una riga specifica, l' thenAPI è un alias che utilizzerà le API AssertJ (simile all'utilizzo assertThat(ex).hasNoCause()...). Ad un certo punto il progetto si è basato su FEST-Assert, l'antenato di AssertJ . EDIT: sembra che il progetto stia producendo un supporto per Java 8 Lambdas.

    Attualmente, questa libreria presenta due carenze:

    • Al momento della stesura di questo articolo, è degno di nota affermare che questa libreria è basata su Mockito 1.x in quanto crea una beffa dell'oggetto testato dietro la scena. Poiché Mockito non è ancora aggiornato, questa libreria non può funzionare con le classi finali o i metodi finali . E anche se fosse basato su Mockito 2 nella versione corrente, ciò richiederebbe di dichiarare un mock maker globale ( inline-mock-maker), qualcosa che potrebbe non essere quello che desideri, poiché questo mock maker ha diversi svantaggi rispetto al normale mock maker.

    • Richiede ancora un'altra dipendenza del test.

    Questi problemi non si applicheranno quando la libreria supporta lambdas. Tuttavia, la funzionalità verrà duplicata dal set di strumenti AssertJ.

    Tenendo conto di tutto se non si desidera utilizzare lo strumento catch-exception, consiglierò il vecchio buon metodo del blocco try- catch, almeno fino al JDK7. E per gli utenti di JDK 8 potresti preferire utilizzare AssertJ in quanto offre molto più che affermare eccezioni.

  2. Con il JDK8, i lambda entrano nella scena del test e hanno dimostrato di essere un modo interessante per affermare un comportamento eccezionale. AssertJ è stato aggiornato per fornire una buona API fluida per affermare comportamenti eccezionali.

    E un test di esempio con 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. Con una riscrittura quasi completa di JUnit 5, le asserzioni sono state leggermente migliorate , possono rivelarsi interessanti come un modo pronto per far valere un'eccezione adeguata. Ma in realtà l'API di asserzione è ancora un po 'scarsa, non c'è niente al di fuori assertThrows.

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

    Come hai notato, assertEqualssta ancora tornando void, e come tale non consente di concatenare affermazioni come AssertJ.

    Inoltre, se ricordi il nome scontro con Matchero Assert, preparati a incontrare lo stesso scontro con Assertions.

Vorrei concludere che oggi (03-03-2017) la facilità d'uso di AssertJ , l'API rilevabile, il rapido ritmo di sviluppo e come dipendenza test di fatto è la soluzione migliore con JDK8 indipendentemente dal framework di test (JUnit o no), JDK precedenti dovrebbero invece fare affidamento su try-catch blocchi, anche se si sentono goffo.

Questa risposta è stata copiata da un'altra domanda che non ha la stessa visibilità, io sono lo stesso autore.


1
Aggiunta di org.junit.jupiter: junit-jupiter-engine: dipendenza 5.0.0-RC2 (oltre alla junit già esistente: junit: 4.12) per poter usare assertThrows non è forse la soluzione preferita, ma non ha causato alcun problemi per me.
anre,

Sono un fan dell'utilizzo della regola ExpectedException ma mi ha sempre infastidito il fatto che si rompa con AAA. Hai scritto un eccellente articolo per descrivere tutti i diversi approcci e mi hai decisamente incoraggiato a provare AssertJ :-) Grazie!
Pim Hazebroek,

@PimHazebroek grazie. L'API di AssertJ è piuttosto ricca. Meglio secondo me ciò che JUnit propone fuori dagli schemi.
Brice,

64

Ora che JUnit 5 e JUnit 4.13 sono stati rilasciati, l'opzione migliore sarebbe quella di utilizzare Assertions.assertThrows() (per JUnit 5) e Assert.assertThrows()(per JUnit 4.13). Vedi la Guida dell'utente di Junit 5 .

Ecco un esempio che verifica che venga generata un'eccezione e utilizza Truth per fare affermazioni sul messaggio di eccezione:

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

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

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

I vantaggi rispetto agli approcci nelle altre risposte sono:

  1. Integrato in JUnit
  2. Viene visualizzato un utile messaggio di eccezione se il codice in lambda non genera un'eccezione e uno stacktrace se genera un'eccezione diversa
  3. Conciso
  4. Consente ai test di seguire Arrange-Act-Assert
  5. Puoi indicare con precisione quale codice ti aspetti di generare l'eccezione
  6. Non è necessario elencare l'eccezione prevista nella throwsclausola
  7. È possibile utilizzare il framework di asserzioni di propria scelta per fare asserzioni sull'eccezione rilevata

Un metodo simile verrà aggiunto a org.junit AssertJUnit 4.13.


Questo approccio è chiaro, ma non vedo come questo consenta al nostro test di seguire "Arrange-Act-Assert", poiché dobbiamo avvolgere la parte "Act" in un "assertThrow", che è un'asserzione.
Clockwork

@Clockwork La lambda è l '"atto". L'obiettivo di Arrange-Act-Assert è quello di rendere il codice pulito e semplice (e quindi facile da capire e mantenere). Come hai affermato, questo approccio è pulito.
NamshubWriter

Speravo ancora di poter far valere il tiro e l'eccezione alla fine del test, nella parte "asserire". In questo approccio, è necessario avvolgere l'atto in una prima asserzione per prenderlo per primo.
Orologio

Ciò richiederebbe più codice in ogni test per fare l'affermazione. Questo è più codice e sarebbe soggetto a errori.
NamshubWriter

43

Che ne dici di questo: prendi un'eccezione molto generale, assicurati che lo faccia uscire dal blocco catch, quindi asserisci che la classe dell'eccezione è quella che ti aspetti che sia. Questa affermazione fallirà se a) l'eccezione è del tipo sbagliato (ad es. Se invece hai ottenuto un puntatore null) eb) l'eccezione non è mai stata lanciata.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

3
Inoltre, non vedrai che tipo di Eccezione ex è nei risultati del test quando arriva il giorno in cui il test fallisce.
jontejj

Questo può essere migliorato un po 'cambiando il modo in cui affermi alla fine. assertEquals(ExpectedException.class, e.getClass())ti mostrerà i valori previsti ed effettivi quando il test fallisce.
Cypher

37

Soluzione stile BDD : JUnit 4 + Catch Exception + AssertJ

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

@Test
public void testFooThrowsIndexOutOfBoundsException() {

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

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

}

dipendenze

eu.codearte.catch-exception:catch-exception:2.0

36

Usando un'asserzione AssertJ , che può essere usata insieme a JUnit:

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

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

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

È meglio che @Test(expected=IndexOutOfBoundsException.class)perché garantisce che la riga prevista nel test abbia generato l'eccezione e ti consenta di controllare più dettagli sull'eccezione, come ad esempio il messaggio, più facilmente:

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

Istruzioni Maven / Gradle qui.


il modo più conciso e nessuno lo apprezza, strano .. Ho solo un problema con la libreria assertJ, asserire che è in conflitto con il nome di junit's. altro su assertJ throwby: JUnit: test delle eccezioni con Java 8 e AssertJ 3.0.0 ~
Codeleak.pl

@ycomp Beh, è ​​una nuova risposta a una domanda molto vecchia, quindi la differenza di punteggio è ingannevole.
Weston,

Questa è probabilmente la soluzione migliore se si possono usare Java 8 e AssertJ!
Pierre Henry,

@ycomp Sospetto che questo conflitto di nomi possa essere legato alla progettazione: la libreria AssertJ ti incoraggia quindi fortemente a non usare mai JUnit assertThat, sempre quella AssertJ. Anche il metodo JUnit restituisce solo un tipo "normale", mentre il metodo AssertJ restituisce una AbstractAssertsottoclasse ... consentendo la stringa di metodi come sopra (o qualunque siano i termini tecnici per questo ...).
mike rodent,

@weston in realtà ho appena usato la tua tecnica in AssertJ 2.0.0. Nessuna scusa per non aggiornare, senza dubbio, ma potresti voler sapere.
mike rodent,

33

Per risolvere lo stesso problema ho impostato un piccolo progetto: http://code.google.com/p/catch-exception/

Usando questo piccolo aiutante scriverai

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

Questo è meno dettagliato della regola ExpectedException di JUnit 4.7. In confronto alla soluzione fornita da skaffman, puoi specificare in quale riga di codice ti aspetti l'eccezione. Spero che questo possa essere d'aiuto.


Ho pensato di fare anche qualcosa del genere, ma alla fine ho scoperto che il vero potere di ExpectedException è che non solo puoi specificare l'eccezione prevista, ma puoi anche specificare alcune proprietà dell'eccezione come la causa prevista o il messaggio previsto.
Jason Thompson,

La mia ipotesi è che questa soluzione abbia alcuni degli stessi inconvenienti delle beffe? Per esempio, se foosi finalfallirà, perché non si può procura foo?
Tom,

Tom, se doStuff () fa parte di un'interfaccia, l'approccio proxy funzionerà. Altrimenti questo approccio fallirà, hai ragione.
Rwitzel,

31

Aggiornamento: JUnit5 ha un miglioramento per testare le eccezioni: assertThrows.

il seguente esempio è tratto da: Junit 5 User Guide

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

Risposta originale utilizzando JUnit 4.

Esistono diversi modi per verificare che venga generata un'eccezione. Ho anche discusso le seguenti opzioni nel mio post Come scrivere grandi test unitari con JUnit

Impostare il expectedparametro @Test(expected = FileNotFoundException.class).

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

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

}

Test con la ExpectedExceptionregola.

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

Puoi leggere di più sui test delle eccezioni nella wiki di JUnit4 per i test delle eccezioni e bad.robot - Expecting Exceptions JUnit Rule .


22

Puoi anche fare questo:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

12
Nei test JUnit, è meglio usare Assert.fail(), non assertnel caso in cui i test vengano eseguiti in un ambiente in cui le asserzioni non sono abilitate.
NamshubWriter

14

IMHO, il modo migliore per verificare le eccezioni in JUnit è il modello 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()); 
}

Il assertTruepotrebbe essere un po 'forte per alcune persone, quindi assertThat(e.getMessage(), containsString("the message");potrebbe essere preferibile.



13

La risposta più flessibile ed elegante per Junit 4 che ho trovato nel blog di Mkyong . Ha la flessibilità di try/catchusare l' @Ruleannotazione. Mi piace questo approccio perché puoi leggere attributi specifici di un'eccezione personalizzata.

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

    }

}

12

Ho provato molti dei metodi qui, ma erano complicati o non soddisfacevano perfettamente i miei requisiti. In effetti, si può scrivere un metodo helper semplicemente:

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

Usalo in questo modo:

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

Zero dipendenze: nessuna necessità di mockito, nessuna necessità di powermock; e funziona perfettamente con le classi finali.


Interessante, ma non adatto ad AAA (Arrange Act Assert), in cui si desidera eseguire l'atto e l'asserzione in fasi effettivamente diverse.
bln-tom,

1
@ bln-tom Tecnicamente sono due passaggi diversi, non sono proprio in questo ordine. ; p
Trejkaz,

10

Soluzione Java 8

Se desideri una soluzione che:

  • Utilizza Java 8 lambdas
  • Non non dipendere da nessuna magia JUnit
  • Consente di verificare più eccezioni all'interno di un singolo metodo di prova
  • Verifica la presenza di un'eccezione generata da una serie specifica di righe all'interno del metodo di test anziché da qualsiasi riga sconosciuta nell'intero metodo di test
  • Produce l'oggetto eccezione reale che è stato lanciato in modo da poterlo esaminare ulteriormente

Ecco una funzione di utilità che ho scritto:

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

( tratto dal mio blog )

Usalo come segue:

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


8

Nel mio caso ottengo sempre RuntimeException da db, ma i messaggi differiscono. E l'eccezione deve essere gestita rispettivamente. Ecco come l'ho provato:

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

1
prima della riga } catch (, è necessario inserirefail("no exception thrown");
Daniel Alder il

6

Basta creare un Matcher che può essere spento e acceso, in questo modo:

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

Per usarlo:

aggiungi public ExpectedException exception = ExpectedException.none();, quindi:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();

6

In JUnit 4 o versioni successive è possibile verificare le eccezioni come segue

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


questo fornisce molte funzionalità che possono essere utilizzate per migliorare i nostri test JUnit.
Se vedi l'esempio di seguito sto testando 3 cose sull'eccezione.

  1. Il tipo di eccezione generata
  2. Il messaggio di eccezione
  3. La causa dell'eccezione


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

}

6

È possibile utilizzare un'asserzione non riuscita dopo il metodo che deve restituire un'eccezione:

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

3
Il secondo catchinghiottirebbe la traccia dello stack se viene generata un'altra eccezione, perdendo informazioni utili
NamshubWriter

5

In aggiunta a quanto detto da NamShubWriter , assicurati che:

  • L'istanza ExpectedException è pubblica ( domanda correlata )
  • ExpectedException non è istanziato, per esempio, il metodo @Before. Questo post spiega chiaramente tutte le complessità dell'ordine di esecuzione di JUnit.

Do Non fare questo:

@Rule    
public ExpectedException expectedException;

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

Infine, questo post sul blog illustra chiaramente come affermare che viene generata una certa eccezione.


4

Consiglio la libreria assertj-coreper gestire l'eccezione nel test junit

In java 8, in questo modo:

//given

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

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

2

La soluzione Junit4 con Java8 consiste nell'utilizzare questa funzione:

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

L'utilizzo è quindi:

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

Si noti che l'unica limitazione è utilizzare un finalriferimento all'oggetto nell'espressione lambda. Questa soluzione consente di continuare affermazioni di test invece di aspettarsi thowable a livello di metodo utilizzando la @Test(expected = IndexOutOfBoundsException.class)soluzione.


1

Ad esempio, si desidera scrivere Junit per il frammento di codice riportato di seguito

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

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

Il codice sopra serve per verificare alcune eccezioni sconosciute che possono verificarsi e quello sotto è per far valere alcune eccezioni con un messaggio personalizzato.

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

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;

               }

    }

Ecco un altro modo per verificare il metodo generato o meno l'eccezione corretta.


1

Il framework JUnit ha il assertThrows()metodo:

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

0

Con Java 8 è possibile creare un metodo prendendo un codice per verificare e l'eccezione prevista come parametri:

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

e poi all'interno del test:

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

Benefici:

  • non fare affidamento su nessuna libreria
  • controllo localizzato - più preciso e consente di avere più asserzioni come questa all'interno di un test, se necessario
  • facile da usare
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.