2009-04-24 10 views
27

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.In Java, come posso convalidare un'eccezione generata con JUnit?

Ad esempio, si consideri una classe che dovrebbe rilevare un'eccezione da qualche altra interfaccia, avvolgere tale eccezione e lanciare l'eccezione avvolta. Si consiglia di verificare:

  1. La chiamata di 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 è che si vuole essere perf convalida aggiuntiva di un'eccezione in una prova di unità (non un dibattito sul fatto che si dovrebbe verificare le cose come il messaggio di eccezione).

Qual è un buon approccio per questo?

+0

Ti sarei grato se tu fossi in grado di verificare la risposta più adatta come quella corretta. – guerda

+1

Non ci si deve preoccupare del metodo * which * che genera l'eccezione: questo è un dettaglio di implementazione. – Raedwald

risposta

20

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

Ecco esempio dalla 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?"); 
    } 
} 
+0

Ho cambiato la risposta accettata a questo perché sentivo che ci sarebbe voluto troppo tempo per arrivare in cima e questo è probabilmente il più aggiornato. Nota Attualmente non lavoro su Java quotidianamente, quindi non sono sicuro di quanto questo nuovo approccio venga adottato. – Iain

2

Il seguente metodo di supporto (adattato da this post sul 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<? extends Throwable> 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 test può quindi richiamare in questo modo:

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

C'è un modo per comprimere la classe/metodo in linea in Java? – Iain

24

Come previsto your answer, si tratta di un buon approccio Inoltre:

È possibile eseguire il wrapping della funzione expectException in una nuova annotazione, denominata ExpectedException.
Un metodo annotato sarebbe simile a questa:

@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 piace Annotazioni :)

+2

in questo modo è necessario estendere il test runner per tenere in considerazione la @ExpectedException – dfa

+1

Questa è sicuramente una buona risposta. Molto leggibile, che IMO è una delle proprietà del codice ben scritto –

+0

Edison: Non ho ancora scritto l'annotazione ;-) – guerda

11

Per JUnit 3.x

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

Dolce ....... !!! – Rachel

5

Fino a questo post che ho fatto la mia convalida eccezione in questo modo:

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

Ho trascorso alcuni momenti a pensare al problema e ho trovato il seguente (Java5, JUnit 3.x):

// Functor interface for exception assertion. 
public interface AssertionContainer<T extends Throwable> { 
    void invoke() throws T; 
    void validate(T throwable); 
    Class<T> getType(); 
} 

// Actual assertion method. 
public <T extends Throwable> void assertThrowsException(AssertionContainer<T> 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<ServletException> functor = new AssertionContainer<ServletException>() { 
    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<ServletException> 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 obiettivo (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.

Quindi, ancora una volta, forse il mio risultato di 10 minuti di risoluzione dei problemi di venerdì sera non è il modo più intelligente per farlo.

+1

+1 Mi piace. Ti fornisce un set standard di convalida insieme a un meccanismo per estendere tale convalida in base al test. Penso che potrebbe essere più leggibile se la classe funtore fosse definita in linea. – Iain

+0

Ho pensato che un po 'di più, alcuni del codice possono essere eliminati con classi astratte che implementano fe. il tipo e convalidare i metodi in modo che si possa semplicemente scrivere assertThrowsException (new ThrowNPE() {public void invoke() {String s = null; s.charAt (null);}}); Sembra ancora un po 'pesante per me, ma poi di nuovo, questo può aiutare a far valere eccezioni complesse, mentre l'altro approccio è migliore per i casi più semplici. – Esko

2

ho fatto qualcosa di molto semplice

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

Perché non fai quanto segue? prova {someFailingMethod(); fallire(); } catch (Exception e) {// nothing} È più leggibile – guerda

+0

true, ha fatto proprio questo, appena ricordato che è sbagliato .. –

+0

Qualcosa di più complesso è praticamente inutile. – soru

18

Guardando le risposte proposte, si può veramente 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()); 
    } 
} 
0

ho fatto un aiutante simile agli altri quelli pubblicati:

public class ExpectExceptionsExecutor { 

    private ExpectExceptionsExecutor() { 
    } 

    public static void execute(ExpectExceptionsTemplate e) { 
     Class<? extends Throwable> 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 il cliente dovrebbe attuare

public interface ExpectExceptionsTemplate<T extends Throwable> { 


    /** 
    * Specify the type of exception that doInttemplate is expected to throw 
    * @return 
    */ 
    Class<T> getExpectedException(); 


    /** 
    * Execute risky code inside this method 
    * TODO specify expected exception using an annotation 
    */ 
    public void doInttemplate(); 

} 

E il codice del client sarebbe qualcosa di simile:

@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 dettagliato 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 dal IDE: D)

4

@akuhn:

Anche senza chiusure possiamo ottenere una soluzione più leggibile (utilizzando catch-exception):

import static com.googlecode.catchexception.CatchException.*; 

public void test() { 
    ... 
    ... 
    catchException(nastyBoy).doNastyStuff(); 
    assertTrue(caughtException() instanceof WhateverException); 
    assertEquals("Message", caughtException().getMessage()); 
} 
+0

whoa che mi ha fatto impazzire! – rogerdpack

0

Per JUnit 5 è molto più facile:

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

Restituendo l'oggetto eccezione per sé, assertThrows() vi permette di testare ogni aspetto quanto riguarda la vostra eccezioni gettate.