Domanda

Come posso usare JUnit4 in modo idiomatico per testare che un codice genera 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 che è molto meno kludgy e molto più nello spirito di JUnit per questo tipo di situazioni.

È stato utile?

Soluzione

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

Altri suggerimenti

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

Se non hai eseguito la migrazione a JUnit 5, ma puoi utilizzare JUnit 4.7, puoi 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 (previsto = IndexOutOfBoundsException.class) perché il test fallirà se IndexOutOfBoundsException viene lanciato prima di foo.doStuff ()

Vedi questo articolo per dettagli

Fai attenzione usando l'eccezione prevista, perché afferma solo che il metodo ha lanciato 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.

Come spiegato in precedenza, 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. assertThwn 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.

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

  • per junit4.x, utilizzare l'attributo 'previsto' opzionale di Test annonation

    @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 try / catch 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.
        }
    }
    
  • 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ì

    • il primo modo viene utilizzato quando si desidera verificare solo il tipo di eccezione
    • gli altri tre modi vengono utilizzati quando si desidera ulteriori messaggi di eccezione test
    • se usi junit 3, è preferibile il terzo
    • se ti piace junit 5, allora ti dovrebbe piacere il quarto
  • per ulteriori informazioni, puoi leggere questo documento e guida dell'utente junit5 per i dettagli.

tl; dr

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

  • post-JDK8: utilizzare AssertJ o lambdas personalizzati per affermare il comportamento eccezionale .

Indipendentemente da Junit 4 o JUnit 5.

la lunga storia

È possibile scrivere un fai da te prova - catch o usare gli strumenti JUnit ( @Test ( previsti = ...) o la funzione della regola JUnit @Rule ExpectedException .

Ma in questo modo non sono così eleganti e non mescolano bene leggibilità saggia con altri strumenti. Inoltre, gli strumenti JUnit presentano alcune insidie.

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

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

    • Se il test deve verificare ulteriori elementi sull'eccezione come la causa o il messaggio (i messaggi di eccezione validi 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 un'eccezione, portando a test falsi positivi e non sono sicuro che < em> PMD , findbugs o Sonar forniranno 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 regola ExpectedException è anche un tentativo di correggere le avvertenze precedenti, ma è un po 'scomodo da usare in quanto utilizza uno stile di aspettativa, EasyMock utenti conosce molto bene questo stile. Potrebbe essere utile per alcuni, ma se segui i principi Sviluppo comportamentale guidato (BDD) o Arrange Act Assert (AAA), la regola ExpectedException ha vinto si adattano a quegli stili di scrittura. A parte ciò, potrebbe soffrire dello stesso problema del modo @Test , 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.

    Vedi anche questo comment su JUnit dell'autore di ExpectedException . JUnit 4.13-beta-2 addirittura depreca questo meccanismo:

      

    Richiesta pull # 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 che sono diventato consapevole dopo aver creato questa risposta che sembra promettente, è catch -exception .

    Come dice la descrizione del progetto, consente a un programmatore di scrivere in una riga fluente di codice che rileva l'eccezione e offre questa eccezione per un'asserzione successiva. E puoi utilizzare 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, prendi l'eccezione su una riga specifica, l'API then è un alias che utilizzerà le API AssertJ (simile all'utilizzo di 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 Lambdas Java 8.

    Attualmente questa libreria presenta due carenze:

    • Al momento della stesura di questo documento è degno di nota affermare che questa libreria si basa 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 vuoi, come ha fatto questo mockmaker diversi inconvenienti rispetto al normale beffardo.

    • Richiede ancora un'altra dipendenza del test.

    Questi problemi non si applicheranno quando la libreria supporterà lambda, tuttavia la funzionalità sarà 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 prova - catch blocco, almeno fino al JDK7. E per gli utenti di JDK 8 potresti preferire utilizzare AssertJ in quanto offre non solo 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 migliorato un po ', potrebbero rivelarsi interessanti come un modo pronto per far valere un'eccezione. Ma in realtà l'API di asserzione è ancora un po 'scarsa, non c'è niente al di fuori di 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 restituendo void , e come tale non consente concatenare asserzioni come AssertJ.

    Inoltre, se ricordi lo scontro di nomi con Matcher o Assert , preparati ad affrontare lo stesso scontro con Asserzioni .

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

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

Ora che JUnit 5 è stato rilasciato, l'opzione migliore è utilizzare Assertions.assertThrows () (vedi la Junit 5 User Guide ).

Ecco un esempio che verifica che viene 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. Ricevi un utile messaggio di eccezione se il codice in lambda non genera un'eccezione e uno stacktrace se genera un'eccezione diversa
  3. Concise
  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 clausola genera
  7. Puoi utilizzare il framework di asserzioni di tua scelta per fare affermazioni sull'eccezione rilevata

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

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

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

Utilizzo di un'asserzione AssertJ , 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 che la riga prevista nel test abbia generato l'eccezione e ti permetta di controllare più dettagli sull'eccezione, come un messaggio, più semplice:

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

Istruzioni Maven / Gradle qui.

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

Usando questo piccolo aiuto 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 aiuti.

Aggiornamento: JUnit5 ha migliorato il test delle 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

Imposta il parametro previsti @Test (previsti = FileNotFoundException.class) .

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

Usando prova 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 ExpectedException Regola.

@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 ulteriori informazioni sul test delle eccezioni in Wiki JUnit4 per test delle eccezioni e bad.robot - Expecting Exceptions JUnit Regola .

Puoi anche farlo:

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

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 assertTrue potrebbe essere un po 'forte per alcune persone, quindi assertThat (e.getMessage (), contieneString (" il messaggio "); potrebbe essere preferibile.

Soluzione JUnit 5

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );

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

Ulteriori informazioni su JUnit 5 su http://junit.org/junit5/ docs / corrente / user-guida / # scrittura-test-asserzioni

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.

La risposta più flessibile ed elegante per Junit 4 che ho trovato nella blog di Mkyong . Ha la flessibilità del try / catch usando l'annotazione @Rule . 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("");

    }

}

Soluzione Java 8

Se desideri una soluzione che:

  • Utilizza Java 8 lambdas
  • non dipende da qualsiasi magia di JUnit
  • Ti 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!" );
}

JUnit ha il supporto integrato per questo, con un " previsti " attribuire

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

Crea 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 eccezione ExpectedException pubblica = ExpectedException.none (); , poi:

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

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

}

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

In aggiunta a quanto NamShubWriter , assicurati che:

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

Non non farlo:

@Rule    
public ExpectedException expectedException;

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

Infine, this post sul blog illustra chiaramente come affermare che viene generata una determinata eccezione.

Raccomando la libreria assertj-core per 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(".......);

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

Nota che l'unica limitazione è usare un riferimento all'oggetto final nell'espressione lambda. Questa soluzione consente di continuare le asserzioni di test invece di aspettarsi thowable a livello di metodo utilizzando la soluzione @Test (prevista = IndexOutOfBoundsException.class) .

Prendiamo 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 è per verificare l'eventuale presenza di un'eccezione sconosciuta e quello sotto è per far valere un'eccezione 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"});
}

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 quindi all'interno del test:

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

I vantaggi:

  • 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

La mia soluzione con Java 8 lambdas:

public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable action) throws Throwable {
    try {
        action.run();
        Assert.fail("Did not throw expected " + expected.getSimpleName());
        return null; // never actually
    } catch (Throwable actual) {
        if (!expected.isAssignableFrom(actual.getClass())) { // runtime '!(actual instanceof expected)'
            System.err.println("Threw " + actual.getClass().getSimpleName() 
                               + ", which is not a subtype of expected " 
                               + expected.getSimpleName());
            throw actual; // throw the unexpected Throwable for maximum transparency
        } else {
            return (T) actual; // return the expected Throwable for further examination
        }
    }
}

Devi definire un FunctionalInterface, perché Runnable non dichiara i lanci richiesti.

@FunctionalInterface
public interface ThrowingRunnable {
    void run() throws Throwable;
}

Il metodo può essere utilizzato come segue:

class CustomException extends Exception {
    public final String message;
    public CustomException(final String message) { this.message = message;}
}
CustomException e = assertThrows(CustomException.class, () -> {
    throw new CustomException("Lorem Ipsum");
});
assertEquals("Lorem Ipsum", e.message);

Esistono due modi per scrivere il test case

  1. Annota il test con l'eccezione generata dal metodo. Qualcosa del genere @Test (expected = IndexOutOfBoundsException.class)
  2. Puoi semplicemente catturare l'eccezione nella classe test usando il blocco try catch e affermare sul messaggio che viene lanciato dal metodo nella classe test.

    try{
    }
    catch(exception to be thrown from method e)
    {
         assertEquals("message", e.getmessage());
    }
    

Spero che questo risponda alla tua domanda Buon apprendimento ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top