Domanda Come asserisci che una certa eccezione viene lanciata nei test di JUnit 4?


Come posso utilizzare JUnit4 in modo idiomatico per verificare che qualche codice generi un'eccezione?

Mentre posso certamente fare qualcosa di simile:

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

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

  assertTrue(thrown);
}

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


1620
2017-10-01 06:56


origine


risposte:


JUnit 4 ha il supporto per questo:

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

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


1940
2017-10-01 07:12



modificare Ora che JUnit5 è stato rilasciato, l'opzione migliore sarebbe quella di utilizzare Assertions.assertThrows() (vedere la mia altra risposta).

Se non è stata eseguita la migrazione a JUnit 5, ma è possibile utilizzare JUnit 4.7, è possibile utilizzare ExpectedException Regola:

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 di @Test(expected=IndexOutOfBoundsException.class) perché il test fallirà se IndexOutOfBoundsException viene lanciato prima foo.doStuff()

Vedere Questo articolo per dettagli


1164
2018-05-29 17:16



Fai attenzione usando l'eccezione prevista, perché asserisce solo che il metodo ha gettato quell'eccezione, non a particolare linea di codice nel test.

Io tendo ad usarlo per testare la validazione dei parametri, perché tali metodi sono in genere molto semplici, ma i 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 il giudizio.


408
2017-10-01 09:31



Come già risposto, 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 al metodo o riferimenti del costruttore. assertInserimento accettando che l'interfaccia si aspetti e sia pronta a gestire un'eccezione.

Questa è una tecnica relativamente semplice ma potente.

Dai un'occhiata a questo post del 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.


187
2017-07-07 22:35



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

  • per junit4.x, usa l'attributo 'previsto' opzionale di Annone test

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • per junit4.x, utilizza 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 metodo try / catch largamente usato sotto 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.
        }
    }
    
  • infine, per junit5.x, puoi anche usare assertThrows come segue

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

    • la prima via è usata quando vuoi testare solo il tipo di eccezione
    • gli altri tre modi vengono utilizzati quando si desidera ulteriormente il messaggio di eccezione di test
    • se usi junit 3, allora è preferito il terzo
    • se ti piace junit 5, allora ti piacerebbe il 4 °
  • per maggiori informazioni, puoi leggere questo documento e Guida per l'utente di junit5 per dettagli.


84
2017-08-05 08:05



tl; dr

  • pre-JDK8: raccomanderò il vecchio bene try-catch bloccare.

  • post-JDK8: usa AssertJ o Lambdas personalizzati da asserire eccezionale comportamento.

Indipendentemente da Junit 4 o JUnit 5.

la lunga storia

È possibile scrivere te stesso a fallo da solo  try-catch bloccare o utilizzare gli strumenti JUnit (@Test(expected = ...) o il @Rule ExpectedException Funzione di regola JUnit).

Ma in questo modo non sono così eleganti e non si mescolano bene leggibilità saggia con altri strumenti.

  1. Il try-catch blocco devi scrivere il blocco attorno al comportamento testato, e scrivere l'asserzione nel blocco catch, che può andare bene ma molti scoprono che questo stile interrompe il flusso di lettura di un test. Inoltre è necessario scrivere un Assert.fail alla fine di try bloccare altrimenti il ​​test può perdere un lato delle asserzioni; PMD, findbugs o Sonar individuerà tali problemi.

  2. Il @Test(expected = ...) la funzionalità è interessante in quanto è possibile scrivere meno codice e quindi scrivere questo test è presumibilmente meno incline agli errori di codifica. Ma all'approccio mancano alcune aree.

    • Se il test deve controllare cose addizionali sull'eccezione come la causa o il messaggio (i messaggi di eccezione sono molto importanti, avere un tipo di eccezione preciso potrebbe non essere sufficiente).
    • Inoltre, poiché l'aspettativa è posta nel metodo, a seconda di come viene scritto il codice testato, la parte errata del codice di test può generare un'eccezione, portando a un test falso positivo 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. Il ExpectedException la regola è anche un tentativo di correggere le avvertenze precedenti, ma sembra un po 'scomodo da usare in quanto utilizza uno stile di aspettativa, EasyMock gli utenti conoscono molto bene questo stile. Potrebbe essere conveniente per alcuni, ma se segui Sviluppo guidato dal comportamento (BDD) o Arrange Act Assert (AAA) principi ExpectedException la regola non si adatta a quegli stili di scrittura. A parte ciò potrebbe risentire dello stesso problema del @Test modo, a seconda di dove posizioni 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 inserita prima dell'istruzione del test, interrompe il flusso di lettura se i test seguono BDD o AAA.

    Vedi anche questo commento questione su JUnit dell'autore di ExpectedException.

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

  1. C'è un progetto che ho preso coscienza dopo aver creato questa risposta che sembra promettente, lo è catch-eccezione.

    Come dice la descrizione del progetto, permette a un programmatore di scrivere in una fluente linea di codice che cattura l'eccezione e offre questa eccezione per un'asserzione successiva. 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 è molto semplice, si cattura l'eccezione su una linea specifica, la then API è 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. MODIFICARE: Sembra che il progetto stia producendo un supporto per Java 8 Lambdas.

    Attualmente questa libreria ha due difetti:

    • Al momento della stesura di questo articolo è degno di nota affermare che questa libreria è basata su Mockito 1.x poiché crea una simulazione dell'oggetto testato dietro la scena. Come Mockito non è ancora aggiornato questa libreria non può funzionare con classi finali o metodi finali. E anche se fosse basato su mockito 2 nella versione attuale, questo richiederebbe di dichiarare un creatore di mock globale (inline-mock-maker), qualcosa che potrebbe non essere quello che vuoi, dato che questo mockmaker ha diversi lati negativi del solito mockmaker.

    • Richiede ancora un'altra dipendenza da test.

    Questi problemi non si applicano una volta che la libreria supporterà lambdas, tuttavia la funzionalità verrà duplicata dal set di strumenti AssertJ.

    Tenendo tutto in considerazione se non si desidera utilizzare lo strumento catch-exception, raccomanderò il vecchio buon modo di try-catch blocco, almeno fino al JDK7. E per gli utenti di JDK 8 potresti preferire usare AssertJ in quanto offre più che asserire eccezioni.

  2. Con JDK8, lambda entra nella scena del test e si è dimostrato un modo interessante per affermare comportamenti eccezionali. AssertJ è stato aggiornato per fornire una buona API fluente per affermare comportamenti eccezionali.

    E un test di prova 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 migliorata un po ', possono rivelarsi interessanti come un modo semplice per affermare correttamente l'eccezione. Ma in realtà l'API di asserzione è ancora un po 'scadente, 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 assertEquals sta ancora tornando voide come tale non consente di concatenare affermazioni come AssertJ.

    Anche se ricordi il nome scontro con Matcher o Assert, preparati a incontrare lo stesso scontro con Assertions.

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

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


58
2017-12-07 14:19



BDD Soluzione di stile: JUnit 4 + Eccezione di cattura + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

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

}

Codice sorgente

dipendenze

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

31
2017-11-15 19:17



Che ne dici di questo: cattura un'eccezione molto generale, assicurati che si estenda dal blocco catch, quindi asserisci che la classe dell'eccezione è ciò che ti aspetti che sia. Questa affermazione fallirà se a) l'eccezione è del tipo sbagliato (ad esempio se hai un Puntatore Nullo invece) eb) l'eccezione non è stata mai lanciata.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

30
2017-10-01 07:03



Utilizzando un AssertJ asserzione, che può essere utilizzata 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 di @Test(expected=IndexOutOfBoundsException.class)perché garantisce la riga prevista nel test ha gettato l'eccezione e ti consente di controllare più dettagli sull'eccezione, ad esempio il messaggio, più semplice:

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

Istruzioni Maven / Gradle qui.


30
2018-03-05 11:07



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

Usando questo piccolo aiutante scriverebbe

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

Questo è meno dettagliato della regola ExpectedException di JUnit 4.7. Rispetto alla soluzione fornita da skaffman, puoi specificare in quale linea di codice ti aspetti l'eccezione. Spero che aiuti.


29
2017-10-28 09:26



Ora che JUnit 5 è stato rilasciato, l'opzione migliore è l'utilizzo Assertions.assertThrows() (vedere il Junit 5 Guida per l'utente).

Ecco un esempio che verifica che un'eccezione venga generata e utilizzata Verità per fare asserzioni 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. Costruito in JUnit
  2. Viene visualizzato un messaggio di eccezione utile se il codice nel 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 lanciare l'eccezione
  6. Non è necessario elencare l'eccezione prevista nel file throws clausola
  7. Puoi utilizzare l'assertion framework di tua scelta per fare asserzioni sull'eccezione intercettata

Un metodo simile verrà aggiunto a org.junit Assert in JUnit 4.13.


27
2017-10-01 16:42