2009-04-24 17 views
27

Al escribir pruebas de unidades para una API de Java, puede haber circunstancias en las que desee realizar una validación más detallada de una excepción. Es decir. más de lo que ofrece la anotación @test ofrecida por JUnit.En Java, ¿cómo puedo validar una excepción lanzada con JUnit?

Por ejemplo, considere una clase que debe detectar una excepción de otra interfaz, ajuste esa excepción y ejecute la excepción envuelta. Es posible que desee verificar:

  1. Llamada al método exacto que arroja la excepción envuelta.
  2. Que la excepción del contenedor tiene la excepción original como causa.
  3. El mensaje de la excepción de contenedor.

El punto principal aquí es que usted quiere ser una validación adicional Potencia de una excepción en una prueba unitaria (no es un debate sobre si se debe verificar cosas como el mensaje de excepción).

¿Cuál es un buen enfoque para esto?

+0

Agradecería que pudieras comprobar que la respuesta correcta sea la correcta. – guerda

+1

No debería importarle * qué * método arroja la excepción: es un detalle de implementación. – Raedwald

Respuesta

20

En JUnit 4, se puede se puede hacer fácilmente usando la regla ExpectedException.

Aquí es ejemplo de 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

Cambié la respuesta aceptada a esta porque sentí que llevaría demasiado tiempo subir a la cima y esta es probablemente la más actualizada. Tenga en cuenta que no trabajo en Java en el día a día en este momento, así que no estoy seguro de cuánto se está adoptando este nuevo enfoque. – Iain

2

El siguiente método de ayuda (adaptado de this publicación de blog) hace el truco:

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

El código de prueba puede entonces invocar este de la siguiente manera:

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

¿Hay alguna forma de comprimir la clase/método en línea en Java? – Iain

24

a lo dispuesto en your answer, es una buen enfoque. Además de esto:

Puede ajustar la función expectException en una nueva Anotación, llamada ExpectedException.
Un método anotada podría tener este aspecto:

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

De esta manera sería más fácil de leer, pero es exactamente el mismo enfoque.

Otra razón es la siguiente: me gusta :) Anotaciones

+2

de esta manera debe extender el corrector de prueba para tener en cuenta la @ExpectedException – dfa

+1

. Esa es definitivamente una buena respuesta. Muy legible, que IMO es una de las propiedades del código bien escrito –

+0

Edison: No he escrito la anotación aún ;-) – guerda

11

Para JUNIT 3.x

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

Dulce ....... !!! – Rachel

5

Hasta este post he hecho mi validación excepción al hacer esto:

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

Sin embargo, me tomé unos momentos pensando en el problema y se me ocurrió lo siguiente (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); 

Al mirar estos dos no puedo decidir cuál me gusta más. Supongo que este es uno de esos problemas en los que el logro de un objetivo (en mi caso, el método de aserción con el parámetro de functor) no vale la pena a largo plazo, ya que es mucho más fácil hacer esos 6+ de código para afirmar el intento Bloque de captura.

Por otra parte, tal vez mi resultado de 10 minutos para resolver problemas el viernes por la noche simplemente no sea la forma más inteligente de hacerlo.

+1

+1 Me gusta esto. Le brinda un conjunto estándar de validación junto con un mecanismo para extender esa validación según cada prueba. Creo que podría ser más legible si la clase de functor se definió en línea. – Iain

+0

Pensé que esto es un poco más, algunos de los códigos se pueden eliminar con clases abstractas que implementan fe. el tipo y valida los métodos para que uno solo pueda escribir assertThrowsException (new ThrowNPE() {public void invoke() {String s = null; s.charAt (null);}}); Todavía parece un poco pesado para mí, pero una vez más, esto puede ayudar a afirmar excepciones complejas, mientras que el otro enfoque es mejor para casos más simples. – Esko

2

hice algo muy simple

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

¿Por qué no haces lo siguiente? try {someFailingMethod(); fallar(); } catch (Exception e) {// nothing} Es más legible – guerda

+0

cierto, hizo exactamente eso, lo recordó mal ... –

+0

Cualquier cosa más compleja es bastante inútil. – soru

18

En cuanto a las respuestas propuestas, puede realmente sentir el dolor de no tener cierres en Java. En mi humilde opinión, la solución más fácil de leer es la captura de prueba.

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

hice un ayudante similar a los otros anunciados los:

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


} 

Y la plantilla el cliente debe aplicar

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

} 

Y el código de cliente sería algo como esto:

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

      @Override 
      public void doInttemplate() { 
       riskyMethod.doSomething(null); 
      } 
     }); 
    } 

Parece realmente prolijo, pero si usa un IDE con buena autocompletación, solo necesitará escribir el tipo de excepción y el código real bajo prueba. (El resto se hará por el IDE: D)

4

@akuhn:

Incluso sin cierres podemos obtener una solución más fácil de leer (utilizando catch-exception):

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

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

whoa que casi me voló la cabeza! – rogerdpack

0

Para JUnit 5 es mucho más fácil:

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

Al devolver el propio objeto de excepción, assertThrows() le permite probar todos los aspectos con respecto a su arrojado excepciones.

Cuestiones relacionadas