Pregunta ¿Cómo se afirma que se produce una cierta excepción en las pruebas JUnit 4?


¿Cómo puedo usar JUnit4 idiomáticamente para probar que algún código arroja una excepción?

Si bien puedo hacer algo como esto:

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

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

  assertTrue(thrown);
}

Recuerdo que hay una anotación o un Assert.xyz o alguna cosa eso es mucho menos kludgy y mucho más en el espíritu de JUnit para este tipo de situaciones.


1621
2017-10-01 06:56


origen


Respuestas:


JUnit 4 tiene soporte para esto:

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

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


1942
2017-10-01 07:12



Editar Ahora que JUnit5 ha lanzado, la mejor opción sería usar Assertions.assertThrows() (ver mi otra respuesta)

Si no ha migrado a JUnit 5, pero puede usar JUnit 4.7, puede usar ExpectedException Regla:

public class FooTest {
  @Rule
  public final ExpectedException exception = ExpectedException.none();

  @Test
  public void doStuffThrowsIndexOutOfBoundsException() {
    Foo foo = new Foo();

    exception.expect(IndexOutOfBoundsException.class);
    foo.doStuff();
  }
}

Esto es mucho mejor que @Test(expected=IndexOutOfBoundsException.class) porque la prueba fallará si IndexOutOfBoundsException es arrojado antes foo.doStuff()

Ver Este artículo para detalles


1165
2018-05-29 17:16



Tenga cuidado al usar la excepción esperada, ya que solo afirma que el método lanzó esa excepción, no una línea particular de código en la prueba.

Tiendo a usar esto para probar la validación de parámetros, ya que estos métodos suelen ser muy simples, pero las pruebas más complejas se pueden realizar mejor con:

try {
    methodThatShouldThrow();
    fail( "My method didn't throw when I expected it to" );
} catch (MyException expectedException) {
}

Aplica juicio.


408
2017-10-01 09:31



Como se contestó anteriormente, hay muchas formas de tratar las excepciones en JUnit. Pero con Java 8 hay otro: usar Lambda Expressions. Con Lambda Expressions podemos lograr una sintaxis como esta:

@Test
public void verifiesTypeAndMessage() {
    assertThrown(new DummyService()::someMethod)
            .isInstanceOf(RuntimeException.class)
            .hasMessage("Runtime exception occurred")
            .hasMessageStartingWith("Runtime")
            .hasMessageEndingWith("occurred")
            .hasMessageContaining("exception")
            .hasNoCause();
}

assertThrown acepta una interfaz funcional, cuyas instancias se pueden crear con expresiones lambda, referencias de métodos o referencias de constructores. assertThrown acepta que la interfaz esperará y estará lista para manejar una excepción.

Esta es una técnica relativamente simple pero poderosa.

Eche un vistazo a esta publicación de blog que describe esta técnica: http://blog.codeleak.pl/2014/07/junit-testing-exception-with-java-8-and-lambda-expressions.html

El código fuente se puede encontrar aquí: https://github.com/kolorobot/unit-testing-demo/tree/master/src/test/java/com/github/kolorobot/exceptions/java8

Divulgación: soy el autor del blog y del proyecto.


187
2017-07-07 22:35



en junit, hay cuatro formas de probar la excepción.

  • para junit4.x, use el atributo opcional 'esperado' de la anotación de prueba

    @Test(expected = IndexOutOfBoundsException.class)
    public void testFooThrowsIndexOutOfBoundsException() {
        foo.doStuff();
    }
    
  • para junit4.x, use la regla 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();
        }
    }
    
  • también puede usar el modo clásico try / catch ampliamente utilizado en el marco de junit 3

    @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.
        }
    }
    
  • finalmente, para junit5.x, también puedes usar assertThrows como sigue

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

    • la primera forma se usa cuando solo desea probar el tipo de excepción
    • las otras tres formas se usan cuando desea enviar un mensaje de excepción de prueba
    • si usas junit 3, entonces el tercero es preferido
    • si te gusta junit 5, entonces te gustará el 4to
  • para más información, puedes leer este documento y Guía del usuario de junit5 para detalles.


84
2017-08-05 08:05



tl; dr

  • pre-JDK8: recomendaré el viejo bueno try-catch bloquear.

  • post-JDK8: use AssertJ o lambdas personalizados para afirmar excepcional comportamiento.

Independientemente de Junit 4 o JUnit 5.

la larga historia

Es posible escribir un hazlo tu mismo  try-catch bloquear o usar las herramientas JUnit (@Test(expected = ...) o el @Rule ExpectedException Característica de la regla JUnit).

Pero de esta manera no son tan elegantes y no se mezclan bien legibilidad sabia con otras herramientas.

  1. los try-catch bloquear tiene que escribir el bloque alrededor del comportamiento probado y escribir la afirmación en el bloque catch, eso puede estar bien, pero muchos encuentran que este estilo interrumpe el flujo de lectura de una prueba. También necesitas escribir un Assert.fail al final de try bloquear de lo contrario, la prueba puede perder un lado de las afirmaciones; PMD, findbugs o Sonar detectará tales problemas.

  2. los @Test(expected = ...) La característica es interesante ya que puede escribir menos código y luego escribir esta prueba es supuestamente menos propenso a errores de codificación. Pero Este enfoque carece de algunas áreas.

    • Si la prueba necesita verificar cosas adicionales en la excepción como la causa o el mensaje (los buenos mensajes de excepción son realmente importantes, tener un tipo de excepción precisa puede no ser suficiente).
    • Además, a medida que se pone la expectativa en el método, dependiendo de cómo se escriba el código probado, entonces la parte incorrecta del código de prueba puede arrojar la excepción, lo que lleva a una prueba de falso positivo y no estoy seguro de que PMD, findbugs o Sonar dará pistas sobre dicho código.

      @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. los ExpectedException la regla también es un intento de corregir las advertencias previas, pero se siente un poco incómodo de usar, ya que utiliza un estilo de expectativa, EasyMock los usuarios conocen muy bien este estilo. Puede ser conveniente para algunos, pero si sigues Desarrollo impulsado por el comportamiento (BDD) o Organizar Acto de Acto (AAA) principios del ExpectedException la regla no encajará en esos estilos de escritura. Aparte de eso, puede sufrir el mismo problema que el @Test manera, dependiendo de dónde coloque la expectativa.

    @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
    }
    

    Incluso la excepción esperada se coloca antes de la declaración de la prueba, rompe su flujo de lectura si las pruebas siguen BDD o AAA.

    Ver también esto comentario cuestión en JUnit del autor de ExpectedException.

Entonces estas opciones anteriores tienen toda su carga de advertencias, y claramente no son inmunes a los errores del codificador.

  1. Hay un proyecto del que me di cuenta después de crear esta respuesta que parece prometedora, es catch-exception.

    Como dice la descripción del proyecto, permite que un codificador escriba una línea fluida de código que capte la excepción y ofrezca esta excepción para una afirmación posterior. Y puedes usar cualquier biblioteca de aserciones como Hamcrest o AssertJ.

    Un rápido ejemplo tomado de la página de inicio:

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

    Como puede ver, el código es muy sencillo, capta la excepción en una línea específica, el then API es un alias que usará API AssertJ (similar al uso assertThat(ex).hasNoCause()...) En algún momento, el proyecto se basó en FEST-Assert the ancestro of AssertJ. EDITAR: Parece que el proyecto está elaborando un soporte Java 8 Lambdas.

    Actualmente esta biblioteca tiene dos deficiencias:

    • En el momento de escribir estas líneas, vale la pena mencionar que esta biblioteca se basa en Mockito 1.x ya que crea una simulación del objeto probado detrás de la escena. Como Mockito aún no está actualizado esta biblioteca no puede funcionar con clases finales o métodos finales. E incluso si estuviera basado en mockito 2 en la versión actual, esto requeriría declarar un simulacro global (inline-mock-maker), algo que puede no ser lo que quieres, ya que este modificador tiene diferentes inconvenientes que el simulador habitual.

    • Requiere otra dependencia de prueba.

    Estos problemas no se aplicarán una vez que la biblioteca admita lambdas, sin embargo, la funcionalidad se duplicará con el conjunto de herramientas AssertJ.

    Tomando todo en cuenta si no quiere usar la herramienta de excepción de captura, recomendaré la buena manera antigua de try-catch bloquear, al menos hasta el JDK7. Y para los usuarios de JDK 8, es posible que prefiera utilizar AssertJ, ya que ofrece mucho más que simplemente hacer excepciones.

  2. Con el JDK8, las lambdas entran en la escena de prueba, y han demostrado ser una forma interesante de afirmar un comportamiento excepcional. AssertJ se ha actualizado para proporcionar una buena API fluida para afirmar un comportamiento excepcional.

    Y una prueba de muestra 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 reescritura casi completa de JUnit 5, las afirmaciones han sido mejorado un poco, pueden resultar interesantes como una forma original de afirmar adecuadamente la excepción. Pero realmente la afirmación API sigue siendo un poco pobre, no hay nada afuera assertThrows.

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
        Throwable t = assertThrows(EmptyStackException.class, () -> stack.peek());
    
        Assertions.assertEquals("...", t.getMessage());
    }
    

    Como habrás notado assertEquals todavía está regresando voidy, como tal, no permite encadenar aserciones como AssertJ.

    Además, si recuerdas el nombre choca con Matcher o Assert, prepárate para enfrentar el mismo choque con Assertions.

Me gustaría concluir que hoy (2017-03-03) AssertJes fácil de usar, API detectable, ritmo de desarrollo rápido y como de facto la dependencia de prueba es la mejor solución con JDK8, independientemente del marco de prueba (JUnit o no), los JDK anteriores deberían confiar en try-catch bloquea incluso si se sienten torpes.

Esta respuesta ha sido copiada de otra pregunta que no tienen la misma visibilidad, soy el mismo autor.


58
2017-12-07 14:19



BDD Solución de estilo: JUnit 4 + Catch Exception + AssertJ

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(foo).doStuff();

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

}

Código fuente

Dependencias

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

31
2017-11-15 19:17



Qué tal esto: capte una excepción muy general, asegúrese de que salga del bloque catch, luego afirme que la clase de la excepción es lo que espera que sea. Esta afirmación fallará si a) la excepción es del tipo incorrecto (por ejemplo, si obtuvo un puntero nulo en su lugar) yb) nunca se lanzó la excepción.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}

30
2017-10-01 07:03



Usando un AssertJ aserción, que se puede usar junto con JUnit:

import static org.assertj.core.api.Assertions.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {
  Foo foo = new Foo();

  assertThatThrownBy(() -> foo.doStuff())
        .isInstanceOf(IndexOutOfBoundsException.class);
}

Es mejor que @Test(expected=IndexOutOfBoundsException.class)porque garantiza que la línea esperada en la prueba arrojó la excepción y le permite verificar más detalles acerca de la excepción, como el mensaje, más fácil:

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

Maven / Gradle instrucciones aquí.


30
2018-03-05 11:07