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

2001

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

Si bien ciertamente 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 algo que es mucho menos grosero y mucho más en el espíritu de JUnit para este tipo de situaciones.

SCdF
fuente
21
El problema con cualquier otro enfoque, pero es que siempre terminan la prueba una vez que se ha lanzado la excepción. Yo, por otro lado, a menudo todavía quiero llamar org.mockito.Mockito.verifycon varios parámetros para asegurarme de que ocurrieron ciertas cosas (de modo que se llamó a un servicio de registro con los parámetros correctos) antes de que se lanzara la excepción.
ZeroOne
55
Puede ver cómo hacer una prueba de excepciones en la página wiki de JUnit github.com/junit-team/junit/wiki/Exception-testing
PhoneixS
66
@ZeroOne: para eso tendría dos pruebas diferentes: una para la excepción y otra para verificar la interacción con tu simulacro.
tddmonkey
Hay una manera de hacer esto con JUnit 5, he actualizado mi respuesta a continuación.
Dilini Rajapaksha

Respuestas:

2363

Depende de la versión de JUnit y de las bibliotecas de afirmaciones que use.

La respuesta original para JUnit <= 4.12fue:

@Test(expected = IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {

    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);

}

Aunque la respuesta https://stackoverflow.com/a/31826781/2986984 tiene más opciones para JUnit <= 4.12.

Referencia:

skaffman
fuente
66
Este código no funcionará si espera una excepción solo en algún lugar de su código, y no una manta como esta.
Oh Chin Boon
44
@skaffman Esto no funcionaría con org.junit.experimental.theories.Theory dirigido por org.junit.experimental.theories.Theories
Artem Oboturov
74
Roy Osherove desaconseja este tipo de pruebas de Excepción en The art of Unit Testing , ya que la Excepción podría estar en cualquier lugar dentro de la prueba y no solo dentro de la unidad bajo prueba.
Kevin Wittek
21
No estoy de acuerdo con @ Kiview / Roy Osherove. En mi opinión, las pruebas deben ser por comportamiento, no por implementación. Al probar que un método específico puede arrojar un error, está vinculando sus pruebas directamente a la implementación. Yo diría que las pruebas en el método que se muestra arriba proporcionan una prueba más valiosa. La advertencia que agregaría es que en este caso probaría una excepción personalizada, para saber que estoy obteniendo la excepción que realmente quiero.
nickbdyer
66
Ninguno. Quiero probar el comportamiento de la clase. Lo importante es que si trato de recuperar algo que no está allí, obtengo una excepción. El hecho de que la estructura de datos ArrayListresponda get()es irrelevante. Si en el futuro decidiera pasar a una matriz primitiva, tendría que cambiar esta implementación de prueba. La estructura de datos debe estar oculta, de modo que la prueba pueda centrarse en el comportamiento de la clase .
nickbdyer
1317

Editar: ahora que se han lanzado JUnit 5 y JUnit 4.13, la mejor opción sería usar Assertions.assertThrows() (para JUnit 5) y Assert.assertThrows()(para JUnit 4.13). Vea mi otra respuesta para más detalles.

Si no ha migrado a JUnit 5, pero puede usar JUnit 4.7, puede usar la ExpectedExceptionRegla:

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 IndexOutOfBoundsExceptionse lanza antesfoo.doStuff()

Vea este artículo para más detalles.

NamshubWriter
fuente
14
@skaffman: si he entendido esto correctamente, parece que la excepción se aplica solo en una prueba, no en toda la clase.
bacar
55
Si la excepción que esperamos lanzar es una excepción marcada, ¿deberíamos agregar lanzamientos o try-catch o probar esta situación de otra manera?
Mohammad Jafar Mashhadi
55
@MartinTrummer No se debe ejecutar ningún código después de foo.doStuff () ya que se lanza la excepción y se sale del método. Tener código después de una excepción esperada (con la excepción de cerrar recursos en un finalmente) no es útil de todos modos ya que nunca debería ejecutarse si se lanza la excepción.
Jason Thompson
99
Este es el mejor enfoque. Aquí hay dos ventajas, en comparación con la solución de skaffman. En primer lugar, la ExpectedExceptionclase tiene formas de hacer coincidir el mensaje de la excepción, o incluso escribir su propio marcador que depende de la clase de excepción. En segundo lugar, puede establecer sus expectativas inmediatamente antes de la línea de código que espera lanzar la excepción, lo que significa que su prueba fallará si la línea de código incorrecta arroja la excepción; mientras que no hay forma de hacerlo con la solución de skaffman.
Dawood ibn Kareem
55
@MJafarMash si la excepción que espera lanzar está marcada, entonces agregaría esa excepción a la cláusula throws del método de prueba. Usted hace lo mismo cada vez que prueba un método que se declara que arroja una excepción marcada, incluso si la excepción no se activa en el caso de prueba particular.
NamshubWriter
472

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

Tiendo a usar esto para probar la validación de parámetros, porque tales métodos son generalmente muy simples, pero las pruebas más complejas podrían ser mejor:

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

Aplica el juicio.

daveb
fuente
95
Tal vez soy de la vieja escuela, pero todavía prefiero esto. También me da un lugar para probar la excepción en sí: a veces tengo excepciones con getters para ciertos valores, o simplemente puedo buscar un valor particular en el mensaje (por ejemplo, buscar "xyz" en el mensaje "código no reconocido 'xyz' ").
Rodney Gitzel
3
Creo que el enfoque de NamshubWriter te ofrece lo mejor de ambos mundos.
Eddie
44
Usando ExpectedException podría llamar a N exception.expect por método para probar como esta excepción.expect (IndexOutOfBoundsException.class); foo.doStuff1 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff2 (); exception.expect (IndexOutOfBoundsException.class); foo.doStuff3 ();
user1154664
10
@ user1154664 En realidad, no puedes. Usando ExpectedException solo puede probar que un método arroja una excepción, porque cuando se llama a ese método, la prueba dejará de ejecutarse porque arrojó la excepción esperada.
NamshubWriter
2
Tu primera oración simplemente no es verdad. Cuando se usa ExpectedException, lo normal es establecer la expectativa inmediatamente antes de la línea que espera lanzar la excepción. De esa manera, si una línea anterior arroja la excepción, no activará la regla y la prueba fallará.
Dawood ibn Kareem
213

Como se respondió anteriormente, hay muchas formas de tratar las excepciones en JUnit. Pero con Java 8 hay otro: usar expresiones Lambda. 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();
}

ClaimThrown acepta una interfaz funcional, cuyas instancias se pueden crear con expresiones lambda, referencias de métodos o referencias de constructor. afirmar que 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.

Rafal Borowiec
fuente
2
Me gusta esta solución, pero ¿puedo descargarla de un repositorio de Maven?
Selwyn
@Airduster una implementación de esta idea que está disponible en Maven es stefanbirkner.github.io/vallado
NamshubWriter
66
@CristianoFontes una versión más simple de esta API está programada para JUnit 4.13. Ver github.com/junit-team/junit/commit/…
NamshubWriter
@RafalBorowiec técnicamente, new DummyService()::someMethodes un MethodHandle, pero este enfoque funciona igualmente bien con expresiones lambda.
Andy
@NamshubWriter, parece que el 4 de junio fue abandonado a favor del 5 de junio: stackoverflow.com/questions/156503/…
Vadzim
154

En junio, hay cuatro formas de probar la excepción.

junit5.x

  • para junit5.x, puede usar assertThrowslo siguiente

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

junit4.x

  • 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 clásico método try / catch ampliamente utilizado en el marco del 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.
        }
    }
    
  • entonces

    • si te gusta el 5 de junio, entonces te gustaría el primero
    • la segunda forma se usa cuando solo quieres probar el tipo de excepción
    • los primeros y últimos dos se usan cuando desea probar el mensaje de excepción adicional
    • si usa el 3 de junio, se prefiere el 4to
  • Para obtener más información, puede leer este documento y la guía del usuario de junit5 para obtener más información.

walsh
fuente
66
Para mí esta es la mejor respuesta, cubre todas las formas muy claramente, ¡gracias! Personalmente, sigo usando la tercera opción incluso con Junit4 para facilitar la lectura, para evitar el bloque de captura vacío también puede atrapar Throwable y afirmar el tipo de e
Nicolas Cornette
¿Es posible usar ExpectedException para esperar una excepción marcada?
miuser
Todo lo que es es una acumulación de las tres respuestas principales. En mi opinión, esta respuesta ni siquiera debería haberse publicado si no agrega nada nuevo. Solo respondiendo (una pregunta popular) para el representante. Bastante inútil.
Paul Samsotha
claro, porque puedes pasar cualquier tipo derivado Trowableal método ExpectedException.expect. Por favor vea su firma . @miuser
walsh
116

tl; dr

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

  • pre-JDK8: Voy a recomendar la vieja buena try- catchbloque. ( No olvides agregar una fail()afirmación antes del catchbloque )

Independientemente de Junit 4 o JUnit 5.

la larga historia

Es posible escribir usted mismo y hacerlo usted mismo try : catchbloquee o use las herramientas JUnit ( @Test(expected = ...)o la @Rule ExpectedExceptionfunción de regla JUnit).

Pero estas formas no son tan elegantes y no combinan bien la legibilidad con otras herramientas. Además, las herramientas JUnit tienen algunas dificultades.

  1. El bloque try- catchtiene 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. Además, debe escribir un Assert.failal final del trybloque. De lo contrario, la prueba puede perder un lado de las afirmaciones; PMD , findbugs o Sonar detectarán tales problemas.

  2. La @Test(expected = ...)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 es deficiente en 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 preciso puede no ser suficiente).
    • Además, como la expectativa se coloca en el método, dependiendo de cómo se escriba el código probado, 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. La ExpectedExceptionregla también es un intento de arreglar las advertencias anteriores, pero se siente un poco incómodo de usar ya que usa un estilo de expectativa, los usuarios de EasyMock conocen muy bien este estilo. Puede ser conveniente para algunos, pero si sigue los principios de Desarrollo Conducido por el Comportamiento (BDD) o Arrange Act Assert (AAA), la ExpectedExceptionregla no encajará en ese estilo de escritura. Aparte de eso, puede sufrir el mismo problema que el @Testcamino, 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 prueba, interrumpe su flujo de lectura si las pruebas siguen BDD o AAA.

    Además, vea este comentario en JUnit del autor de ExpectedException. JUnit 4.13-beta-2 incluso desprecia este mecanismo:

    Solicitud de extracción n. ° 1519 : desaprobar excepción esperada

    El método Assert.assertThrows proporciona una mejor manera de verificar excepciones. Además, el uso de ExpectedException es propenso a errores cuando se usa con otras reglas como TestWatcher porque el orden de las reglas es importante en ese caso.

Por lo tanto, 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 prometedor, es una excepción .

    Como dice la descripción del proyecto, permitió que un codificador escribiera en una línea fluida de código para detectar la excepción y ofrecer esta excepción para la última afirmación. Y puede usar cualquier biblioteca de aserciones como Hamcrest o AssertJ .

    Un ejemplo rápido 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 realmente sencillo, detecta la excepción en una línea específica, la thenAPI es un alias que usará API AssertJ (similar a usar assertThat(ex).hasNoCause()...). En algún momento, el proyecto se basó en FEST-Assert, el antepasado de AssertJ . EDITAR: Parece que el proyecto está gestando un soporte para Java 8 Lambdas.

    Actualmente, esta biblioteca tiene dos deficiencias:

    • Al momento de escribir esto, es digno de mención decir 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 todavía no está actualizado, esta biblioteca no puede funcionar con clases finales o métodos finales . E incluso si se basara en Mockito 2 en la versión actual, esto requeriría declarar un creador de simulacros global ( inline-mock-maker), algo que puede no ser lo que desea, ya que este simulador tiene diferentes inconvenientes que el creador de simulacros regular.

    • Requiere otra dependencia de prueba.

    Estos problemas no se aplicarán una vez que la biblioteca admita lambdas. Sin embargo, la funcionalidad será duplicada por el conjunto de herramientas AssertJ.

    Teniendo todo en cuenta si no desea utilizar la herramienta de captura de excepciones, recomendaré la antigua y buena forma del bloque try- catch, al menos hasta el JDK7. Y para los usuarios de JDK 8, es posible que prefiera usar AssertJ, ya que ofrece más que solo afirmar 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 API fluida y agradable 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 se han mejorado un poco, pueden resultar interesantes como una forma inmediata de afirmar correctamente la excepción. Pero realmente la API de afirmación 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 notaron, assertEqualstodavía está regresando void, y como tal no permite encadenar afirmaciones como AssertJ.

    Además, si recuerda el choque de nombres con Matchero Assert, prepárese para enfrentar el mismo choque con Assertions.

Me gustaría concluir que hoy (2017-03-03) la facilidad de uso de AssertJ , la API reconocible , el rápido ritmo de desarrollo y, como una dependencia de prueba de facto , es la mejor solución con JDK8 independientemente del marco de prueba (JUnit o no), los JDK anteriores, deben confiar en su lugar try-catch bloques, incluso si se sienten torpe.

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

Brice
fuente
1
Agregar org.junit.jupiter: junit-jupiter-engine: 5.0.0-RC2 dependencia (además de la ya existente junit: junit: 4.12) para poder usar afirmarThrows quizás no sea la solución preferida, pero no causó ninguna problemas para mí
anre
Soy fanático de usar la regla ExpectedException pero siempre me molestó que rompa con AAA. Has escrito un excelente artículo para describir todos los diferentes enfoques y definitivamente me has animado a probar AssertJ :-) ¡Gracias!
Pim Hazebroek
@PimHazebroek gracias. AssertJ API es bastante rica. Mejor en mi opinión que lo que JUnit propone fuera de la caja.
Brice
64

Ahora que se han lanzado JUnit 5 y JUnit 4.13, la mejor opción sería usar Assertions.assertThrows() (para JUnit 5) y Assert.assertThrows()(para JUnit 4.13). Consulte la Guía del usuario de Junit 5 .

Aquí hay un ejemplo que verifica que se produce una excepción y usa la Verdad para hacer afirmaciones en el mensaje de excepción:

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

    IndexOutOfBoundsException e = assertThrows(
        IndexOutOfBoundsException.class, foo::doStuff);

    assertThat(e).hasMessageThat().contains("woops!");
  }
}

Las ventajas sobre los enfoques en las otras respuestas son:

  1. Construido en JUnit
  2. Obtiene un mensaje de excepción útil si el código en el lambda no arroja una excepción, y un stacktrace si arroja una excepción diferente
  3. Conciso
  4. Permite que tus pruebas sigan Arrange-Act-Assert
  5. Puede indicar con precisión qué código espera lanzar la excepción
  6. No necesita enumerar la excepción esperada en la throwscláusula
  7. Puede usar el marco de afirmación de su elección para hacer afirmaciones sobre la excepción capturada

Se agregará un método similar org.junit Asserten JUnit 4.13.

NamshubWriter
fuente
Este enfoque es limpio, pero no veo cómo esto permite que nuestra prueba siga "Arrange-Act-Assert", ya que tenemos que ajustar la parte "Act" en un "afirmarTiro", que es una afirmación.
Clockwork
@ Clockwork La lambda es el "acto". El objetivo de Arrange-Act-Assert es hacer que el código sea limpio y simple (y, por lo tanto, fácil de entender y mantener). Como dijiste, este enfoque es limpio.
NamshubWriter
Sin embargo, todavía esperaba poder afirmar el lanzamiento y la excepción al final de la prueba, en la parte "afirmar". En este enfoque, debe ajustar el acto en una primera afirmación para atraparlo primero.
Clockwork
Eso requeriría más código en cada prueba para hacer la afirmación. Eso es más código y sería propenso a errores.
NamshubWriter
43

Qué tal esto: Capture 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) la excepción nunca se lanzó.

public void testFooThrowsIndexOutOfBoundsException() {
  Throwable e = null;

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

  assertTrue(e instanceof IndexOutOfBoundsException);
}
Johan
fuente
3
Además, no verá qué tipo de Excepción ex hay en los resultados de la prueba cuando llegue el día en que la prueba falle.
jontejj
Esto se puede mejorar un poco cambiando la forma de afirmar al final. assertEquals(ExpectedException.class, e.getClass())le mostrará los valores esperados y reales cuando la prueba falle.
Cypher
37

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

import static com.googlecode.catchexception.apis.BDDCatchException.*;

@Test
public void testFooThrowsIndexOutOfBoundsException() {

    when(() -> foo.doStuff());

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

}

Dependencias

eu.codearte.catch-exception:catch-exception:2.0
MariuszS
fuente
36

Usando una aserción AssertJ , 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 sobre la excepción, como el mensaje, más fácil:

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

Instrucciones de Maven / Gradle aquí.

Weston
fuente
de manera más concisa y nadie lo aprecia, extraño. Solo tengo un problema con la biblioteca afirmar, afirmar que entra en conflicto con el nombre de junit. Más información sobre afirmar throwby: JUnit: Probar excepciones con Java 8 y AssertJ 3.0.0 ~ Codeleak.pl
ycomp
@ycomp Bueno, es una nueva respuesta a una pregunta muy antigua, por lo que la diferencia de puntuación es engañosa.
Weston
¡Esa es probablemente la mejor solución si uno puede usar Java 8 y AssertJ!
Pierre Henry
@ycomp Sospecho que este conflicto de nombres puede ser por diseño: por lo tanto, la biblioteca AssertJ le recomienda encarecidamente que nunca use el JUnit assertThat, siempre el AssertJ. Además, el método JUnit devuelve solo un tipo "regular", mientras que el método AssertJ devuelve una AbstractAssertsubclase ... que permite la secuencia de métodos como anteriormente (o los términos técnicos para esto ...).
Mike roedor
@weston en realidad, acabo de usar tu técnica en AssertJ 2.0.0. No hay excusa para no actualizar, sin duda, pero aunque te gustaría saberlo.
Mike roedor
33

Para resolver el mismo problema, configuré un pequeño proyecto: http://code.google.com/p/catch-exception/

Usando este pequeño ayudante escribirías

verifyException(foo, IndexOutOfBoundsException.class).doStuff();

Esto es menos detallado que la regla ExpectedException de JUnit 4.7. En comparación con la solución proporcionada por skaffman, puede especificar en qué línea de código espera la excepción. Espero que esto ayude.

rwitzel
fuente
Pensé en hacer algo como esto también, pero finalmente descubrí que el verdadero poder de ExpectedException es que no solo puede especificar la excepción esperada, sino que también puede especificar ciertas propiedades de la excepción, como la causa esperada o el mensaje esperado.
Jason Thompson
Mi conjetura es que esta solución tiene algunos de los mismos inconvenientes que los simulacros. Por ejemplo, si fooes finalque se producirá un error porque no se puede Proxy foo?
Tom
Tom, si doStuff () es parte de una interfaz, el enfoque proxy funcionará. De lo contrario, este enfoque fallará, tienes razón.
rwitzel
31

Actualización: JUnit5 tiene una mejora para probar las excepciones: assertThrows.

el siguiente ejemplo es de: Junit 5 Guía del usuario

 @Test
void exceptionTesting() {
    Throwable exception = assertThrows(IllegalArgumentException.class, () -> 
    {
        throw new IllegalArgumentException("a message");
    });
    assertEquals("a message", exception.getMessage());
}

Respuesta original usando JUnit 4.

Hay varias formas de probar que se produce una excepción. También he discutido las siguientes opciones en mi publicación Cómo escribir excelentes pruebas unitarias con JUnit

Establece el expectedparámetro @Test(expected = FileNotFoundException.class).

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

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

}

Prueba con ExpectedExceptionregla.

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

Puede leer más sobre las pruebas de excepciones en JUnit4 wiki para pruebas de excepción y bad.robot - Regla de JUnit de excepciones de excepción .

Dilini Rajapaksha
fuente
22

También puedes hacer esto:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}
John Mikic
fuente
12
En las pruebas JUnit, es mejor usar Assert.fail(), no assert, solo en caso de que sus pruebas se ejecuten en un entorno donde las aserciones no están habilitadas.
NamshubWriter
14

En mi humilde opinión, la mejor manera de verificar las excepciones en JUnit es el patrón try / catch / fail / afirmar:

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

El assertTruepodría ser un poco fuerte para algunas personas, por lo que assertThat(e.getMessage(), containsString("the message");podría ser preferible.

Alex Collins
fuente
13

La respuesta más flexible y elegante para Junit 4 la encontré en el blog de Mkyong . Tiene la flexibilidad de try/catchusar la @Ruleanotación. Me gusta este enfoque porque puedes leer atributos específicos de una excepción personalizada.

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

    }

}
Dherik
fuente
12

Probé muchos de los métodos aquí, pero eran complicados o no cumplían con mis requisitos. De hecho, uno puede escribir un método auxiliar simplemente:

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

Úselo así:

assertException(new BlastContainer() {
    @Override
    public void test() throws Exception {
        doSomethingThatShouldExceptHere();
    }
});

Cero dependencias: no necesita mockito, no necesita powermock; y funciona bien con las clases finales.

Hugh Perkins
fuente
Interesante, pero no se ajusta a AAA (Arrange Act Assert), donde desea hacer el paso Actuar y Actuar en pasos realmente diferentes.
bln-tom
1
@ bln-tom Técnicamente son dos pasos diferentes, simplemente no están en ese orden. ; p
Trejkaz
10

Solución Java 8

Si desea una solución que:

  • Utiliza Java 8 lambdas
  • No , no depende de ninguna magia JUnit
  • Le permite verificar varias excepciones dentro de un solo método de prueba
  • Comprueba si se produce una excepción por un conjunto específico de líneas dentro de su método de prueba en lugar de cualquier línea desconocida en todo el método de prueba
  • Produce el objeto de excepción real que se lanzó para que pueda examinarlo más a fondo.

Aquí hay una función de utilidad que escribí:

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

( tomado de mi blog )

Úselo de la siguiente manera:

@Test
public void testThrows()
{
    RuntimeException e = expectException( RuntimeException.class, () -> 
        {
            throw new RuntimeException( "fail!" );
        } );
    assert e.getMessage().equals( "fail!" );
}
Mike Nakis
fuente
8

En mi caso, siempre obtengo RuntimeException de db, pero los mensajes difieren. Y la excepción debe ser manejada respectivamente. Así es como lo probé:

@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));
}
Macchiatow
fuente
1
antes de la línea con } catch (, debe insertarfail("no exception thrown");
Daniel Alder
6

Simplemente haga un Matcher que se pueda apagar y encender de esta manera:

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

Para usarlo:

agregar public ExpectedException exception = ExpectedException.none();, entonces:

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();
Tor P
fuente
6

En JUnit 4 o posterior, puede probar las excepciones de la siguiente manera

@Rule
public ExpectedException exceptions = ExpectedException.none();


Esto proporciona muchas características que se pueden utilizar para mejorar nuestras pruebas JUnit.
Si ve el siguiente ejemplo, estoy probando 3 cosas en la excepción.

  1. El tipo de excepción lanzada
  2. El mensaje de excepción
  3. La causa de la excepción.


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

}
JoBⅈN
fuente
6

Podemos usar un error de aserción después del método que debe devolver una excepción:

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());
}
Shessuky
fuente
3
El segundo catchse tragaría el rastro de la pila si se lanza alguna otra excepción, perdiendo información útil
NamshubWriter
5

Además de lo que NamShubWriter ha dicho, asegúrese de que:

  • La instancia de ExpectedException es pública ( pregunta relacionada )
  • La ExpectedException no se instancia en, digamos, el método @Before. Esta publicación explica claramente todas las complejidades de la orden de ejecución de JUnit.

No no hacer esto:

@Rule    
public ExpectedException expectedException;

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

Finalmente, esta publicación de blog ilustra claramente cómo afirmar que se produce una cierta excepción.

Bruce Wayne
fuente
4

Recomiendo la biblioteca assertj-corepara manejar la excepción en prueba junit

En java 8, así:

//given

//when
Throwable throwable = catchThrowable(() -> anyService.anyMethod(object));

//then
AnyException anyException = (AnyException) throwable;
assertThat(anyException.getMessage()).isEqualTo("........");
assertThat(exception.getCode()).isEqualTo(".......);
Piotr R
fuente
2

La solución de Junit4 con Java8 es usar esta función:

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

El uso es entonces:

    assertThrows(ValidationException.class,
            () -> finalObject.checkSomething(null));

Tenga en cuenta que la única limitación es utilizar una finalreferencia de objeto en la expresión lambda. Esta solución permite continuar con las aserciones de prueba en lugar de esperar que sea posible a nivel de método utilizando la @Test(expected = IndexOutOfBoundsException.class)solución.

Donatello
fuente
1

Tomemos, por ejemplo, que desea escribir Junit para el fragmento de código mencionado a continuación

public int divideByZeroDemo(int a,int b){

    return a/b;
}

public void exceptionWithMessage(String [] arr){

    throw new ArrayIndexOutOfBoundsException("Array is out of bound");
}

El código anterior es para probar alguna excepción desconocida que pueda ocurrir y el siguiente es afirmar alguna excepción con un mensaje personalizado.

 @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"});
}
Shirsh Sinha
fuente
1
    @Test(expectedException=IndexOutOfBoundsException.class) 
    public void  testFooThrowsIndexOutOfBoundsException() throws Exception {
         doThrow(IndexOutOfBoundsException.class).when(foo).doStuff();  
         try {
             foo.doStuff(); 
            } catch (IndexOutOfBoundsException e) {
                       assertEquals(IndexOutOfBoundsException .class, ex.getCause().getClass());
                      throw e;

               }

    }

Aquí hay otra forma de verificar si el método arrojó la excepción correcta o no.

MangduYogii
fuente
1

JUnit framework tiene assertThrows()método:

ArithmeticException exception = assertThrows(ArithmeticException.class, () ->
    calculator.divide(1, 0));
assertEquals("/ by zero", exception.getMessage());
Lu55
fuente
0

Con Java 8 puede crear un método tomando un código para verificar y la excepción esperada como parámetros:

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

y luego dentro de tu prueba:

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

Beneficios:

  • no depender de ninguna biblioteca
  • verificación localizada: más precisa y permite tener múltiples afirmaciones como esta dentro de una prueba si es necesario
  • fácil de usar
fahrenx
fuente