In Java, come posso convalidare un’eccezione generata con JUnit?

Durante la scrittura di unit test per un’API Java, potrebbero verificarsi circostanze in cui si desidera eseguire una convalida più dettagliata di un’eccezione. Cioè più di quanto offerto dall’annotazione @test offerta da JUnit.

Ad esempio, si consideri una class che dovrebbe catturare un’eccezione da qualche altra interfaccia, avvolgere quell’eccezione e lanciare l’eccezione avvolta. Potresti voler verificare:

  1. La chiamata al metodo esatto che genera l’eccezione avvolta.
  2. Che l’eccezione del wrapper abbia come causa l’eccezione originale.
  3. Il messaggio dell’eccezione wrapper.

Il punto principale qui è che si vuole essere perf ulteriori convalida di un’eccezione in un test unitario (non un dibattito sull’opportunità di verificare cose come il messaggio di eccezione).

Qual è un buon approccio per questo?

    In JUnit 4 può essere fatto facilmente utilizzando la regola ExpectedException .

    Ecco un esempio di javadocs:

    // These tests all pass. public static class HasExpectedException { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void throwsNothing() { // no exception expected, none thrown: passes. } @Test public void throwsNullPointerException() { thrown.expect(NullPointerException.class); throw new NullPointerException(); } @Test public void throwsNullPointerExceptionWithMessage() { thrown.expect(NullPointerException.class); thrown.expectMessage("happened?"); thrown.expectMessage(startsWith("What")); throw new NullPointerException("What happened?"); } } 

    Come previsto nella tua risposta , è un buon approccio. In aggiunta a questo:

    È ansible includere la funzione expectException in una nuova annotazione, denominata ExpectedException .
    Un metodo annotato sarebbe simile a questo:

     @Test @ExpectedException(class=WrapperException.class, message="Exception Message", causeException) public void testAnExceptionWrappingFunction() { //whatever you test } 

    In questo modo sarebbe più leggibile, ma è esattamente lo stesso approccio.

    Un altro motivo è: mi piacciono le annotazioni 🙂

    Guardando le risposte proposte, puoi davvero sentire il dolore di non avere chiusure in Java. IMHO, la soluzione più leggibile è il buon vecchio tentativo di cattura.

     @Test public void test() { ... ... try { ... fail("No exception caught :("); } catch (RuntimeException ex) { assertEquals(Whatever.class, ex.getCause().getClass()); assertEquals("Message", ex.getMessage()); } } 

    Per JUNIT 3.x

     public void test(){ boolean thrown = false; try{ mightThrowEx(); } catch ( Surprise expected ){ thrown = true; assertEquals( "message", expected.getMessage()); } assertTrue(thrown ); } 

    Fino a questo post ho fatto la mia convalida delle eccezioni in questo modo:

     try { myObject.doThings(); fail("Should've thrown SomeException!"); } catch (SomeException e) { assertEquals("something", e.getSomething()); } 

    Ho trascorso alcuni momentjs a pensare al problema e mi è venuto in mente quanto segue (Java5, JUnit 3.x):

     // Functor interface for exception assertion. public interface AssertionContainer { void invoke() throws T; void validate(T throwable); Class getType(); } // Actual assertion method. public  void assertThrowsException(AssertionContainer functor) { try { functor.invoke(); fail("Should've thrown "+functor.getType()+"!"); } catch (Throwable exc) { assertSame("Thrown exception was of the wrong type! Expected "+functor.getClass()+", actual "+exc.getType(), exc.getClass(), functor.getType()); functor.validate((T) exc); } } // Example implementation for servlet I used to actually test this. It was an inner class, actually. AssertionContainer functor = new AssertionContainer() { public void invoke() throws ServletException { servlet.getRequiredParameter(request, "some_param"); } public void validate(ServletException e) { assertEquals("Parameter \"some_param\" wasn't found!", e.getMessage()); } public Class getType() { return ServletException.class; } } // And this is how it's used. assertThrowsException(functor); 

    Guardando questi due non riesco a decidere quale mi piace di più. Immagino che questo sia uno di quei problemi in cui il raggiungimento di un objective (nel mio caso, il metodo di asserzione con parametro functor) non vale la pena a lungo termine poiché è molto più facile fare quei 6 o più di codice per asserire il tentativo .. blocco di blocco.

    Poi di nuovo, forse il mio risultato di 10 minuti di problem solving di venerdì sera non è il modo più intelligente per farlo.

    @akuhn:

    Anche senza chiusure possiamo ottenere una soluzione più leggibile (usando l’ eccezione di catch ):

     import static com.googlecode.catchexception.CatchException.*; public void test() { ... ... catchException(nastyBoy).doNastyStuff(); assertTrue(caughtException() instanceof WhateverException); assertEquals("Message", caughtException().getMessage()); } 

    Il seguente metodo di supporto (adattato da questo post del blog) fa il trucco:

     /** * Run a test body expecting an exception of the * given class and with the given message. * * @param test To be executed and is expected to throw the exception. * @param expectedException The type of the expected exception. * @param expectedMessage If not null, should be the message of the expected exception. * @param expectedCause If not null, should be the same as the cause of the received exception. */ public static void expectException( Runnable test, Class expectedException, String expectedMessage, Throwable expectedCause) { try { test.run(); } catch (Exception ex) { assertSame(expectedException, ex.getClass()); if (expectedMessage != null) { assertEquals(expectedMessage, ex.getMessage()); } if (expectedCause != null) { assertSame(expectedCause, ex.getCause()); } return; } fail("Didn't find expected exception of type " + expectedException.getName()); } 

    Il codice di prova può quindi richiamarlo come segue:

     TestHelper.expectException( new Runnable() { public void run() { classInstanceBeingTested.methodThatThrows(); } }, WrapperException.class, "Exception Message", causeException ); 

    Ho fatto qualcosa di molto semplice

     testBla(){ try { someFailingMethod() fail(); //method provided by junit } catch(Exception e) { //do nothing } } 

    Ho creato un aiuto simile a quello degli altri postati:

     public class ExpectExceptionsExecutor { private ExpectExceptionsExecutor() { } public static void execute(ExpectExceptionsTemplate e) { Class aClass = e.getExpectedException(); try { Method method = ExpectExceptionsTemplate.class.getMethod("doInttemplate"); method.invoke(e); } catch (NoSuchMethodException e1) { throw new RuntimeException(); } catch (InvocationTargetException e1) { Throwable throwable = e1.getTargetException(); if (!aClass.isAssignableFrom(throwable.getClass())) { // assert false fail("Exception isn't the one expected"); } else { assertTrue("Exception captured ", true); return; } ; } catch (IllegalAccessException e1) { throw new RuntimeException(); } fail("No exception has been thrown"); } } 

    E il modello che il cliente dovrebbe implementare

     public interface ExpectExceptionsTemplate { /** * Specify the type of exception that doInttemplate is expected to throw * @return */ Class getExpectedException(); /** * Execute risky code inside this method * TODO specify expected exception using an annotation */ public void doInttemplate(); } 

    E il codice cliente sarebbe qualcosa del genere:

     @Test public void myTest() throws Exception { ExpectExceptionsExecutor.execute(new ExpectExceptionsTemplate() { @Override public Class getExpectedException() { return IllegalArgumentException.class; } @Override public void doInttemplate() { riskyMethod.doSomething(null); } }); } 

    Sembra davvero prolisso ma se si utilizza un IDE con un buon completamento automatico sarà necessario solo scrivere il tipo di eccezione e il codice effettivo sotto test. (il resto sarà fatto dall’IDE: D)

    Per JUnit 5 è molto più semplice:

      @Test void testAppleIsSweetAndRed() throws Exception { IllegalArgumentException ex = assertThrows( IllegalArgumentException.class, () -> testClass.appleIsSweetAndRed("orange", "red", "sweet")); assertEquals("this is the exception message", ex.getMessage()); assertEquals(NullPointerException.class, ex.getCause().getClass()); } 

    assertThrows() object di eccezione stesso, assertThrows() consente di testare ogni aspetto riguardante le eccezioni generate.