¿Cómo afirmo mi mensaje de excepción con la anotación de prueba JUnit?

8 minutos de lectura

avatar de usuario
Cshah

He escrito algunas pruebas JUnit con @Test anotación. Si mi método de prueba arroja una excepción verificada y si quiero afirmar el mensaje junto con la excepción, ¿hay alguna manera de hacerlo con JUnit? @Test ¿anotación? AFAIK, JUnit 4.7 no proporciona esta función, pero ¿hay versiones futuras que la proporcionen? Sé que en .NET puede afirmar el mensaje y la clase de excepción. Buscando una característica similar en el mundo de Java.

Esto es lo que quiero:

@Test (expected = RuntimeException.class, message = "Employee ID is null")
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() {}

  • Ahora que lo pienso un poco más… ¿Estás seguro de que es una buena idea hacer valer el mensaje? Su pregunta me hizo profundizar un poco en el código fuente de junit y parece que podrían haber agregado esta función fácilmente. El hecho de que lo hicieran no, me hace pensar que podría no considerarse una buena práctica. ¿Por qué es importante en su proyecto hacer valer el mensaje?

    – c_fabricante

    21 de marzo de 2010 a las 23:13

  • buena pregunta. Digamos que un método que contiene 15 líneas de código arroja la misma excepción desde 2 lugares diferentes. Mis casos de prueba deben afirmar no solo la clase de excepción sino también el mensaje que contiene. En un mundo ideal, cualquier comportamiento anormal debería tener su propia excepción. Si ese hubiera sido el caso, mi pregunta nunca surgiría, pero las aplicaciones de producción no tienen su excepción personalizada única para cada comportamiento anormal.

    – Cshah

    22 de marzo de 2010 a las 3:20

  • Como nota al margen – hay @expectedExceptionMessage anotación en PHPUnit.

    – banquero

    2 de febrero de 2020 a las 11:37

avatar de usuario
jesse merriman

Podrías usar el @Rule anotación con ExpectedExceptioncomo esto:

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

@Test
public void shouldThrowRuntimeExceptionWhenEmployeeIDisNull() throws Exception {
    expectedEx.expect(RuntimeException.class);
    expectedEx.expectMessage("Employee ID is null");

    // do something that should throw the exception...
    System.out.println("=======Starting Exception process=======");
    throw new NullPointerException("Employee ID is null");
}

Tenga en cuenta que el ejemplo en el ExpectedException docs es (actualmente) incorrecto: no hay un constructor público, por lo que debe usar ExpectedException.none().

  • Nota: Para mí cuando el expectMessage se especificó como una cadena vacía, la comparación del mensaje no se realizó

    – diablo rojo

    29 de noviembre de 2015 a las 9:23

  • Personalmente, no me gustaría usar esto, ya que la creación de campos para un pequeño subconjunto de métodos es una mala práctica. No es una crítica a la respuesta, sino al diseño de JUnit. La solución hipotética del OP sería mucho mejor si existiera.

    – Sridhar Sarnobat

    22 de julio de 2016 a las 21:07

  • @redDevil: el mensaje esperado comprueba si el mensaje de error “contiene” la cadena especificada en esta función (como una subcadena del mensaje de error)

    – tuan.dinh

    28 de diciembre de 2016 a las 1:28

  • expectMessage con el parámetro de cadena hace una verificación de String.contains, para una coincidencia exacta del mensaje de excepción, use el comparador de hacrest failure.expectMessage(CoreMatchers.equalTo(...))

    – Sivabalan

    30 de mayo de 2017 a las 3:07

  • ExpectedException.none() está en desuso desde Junit 4.13

    – heroína

    30 mayo 2020 a las 15:15

avatar de usuario
Johan Boberg

En JUnit 4.13 puedes hacer:

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

...

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

  assertEquals("a message", exception.getMessage());
}

Esto también funciona en Unidad 5 pero con diferentes importaciones:

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

...

  • Como esta solución. Debería pasar a JUnit 5.

    – WesternGun

    8 de mayo de 2019 a las 10:17

  • Gaaaaaaaaaa. 4.13 todavía en versión beta a partir de hoy (otoño de 2019)? mvnrepository.com/artifact/junit/junit

    – granadaCoder

    10 oct 2019 a las 12:15


  • v4.13 ya no está en estado beta (lanzamiento en enero de 2020)

    – Simón

    2 de junio de 2020 a las 10:39

  • Ya que assertThrows está disponible en JUnit 4.13, esta debería ser la respuesta aceptada

    – vdimitrov

    16 de febrero de 2021 a las 9:52

  • ya estaba usando 4.13 assertThrowspero aún no sabía que returns la excepción para la inspección posterior. +1, exactamente lo que necesitaba 😀

    – Jules Kerssemakers

    4 mayo 2021 a las 12:30


avatar de usuario
tormenta de rayos

me gusta el @Rule responder. Sin embargo, si por alguna razón no desea utilizar reglas. Hay una tercera opción.

@Test (expected = RuntimeException.class)
public void myTestMethod()
{
   try
   {
      //Run exception throwing operation here
   }
   catch(RuntimeException re)
   {
      String message = "Employee ID is null";
      assertEquals(message, re.getMessage());
      throw re;
    }
    fail("Employee Id Null exception did not throw!");
  }

  • (esperado = RuntimeException.class) y throw re; no son necesarios;. Debe haber un método que arroje una excepción dentro de las afirmaciones de prueba y captura.

    – januszj

    30 de septiembre de 2020 a las 11:21

  • @janusz j: personalmente prefiero dejar el (expected... y throw re; líneas, pero elimine las fail(... línea. ¿Puede usted o cualquier otra persona decirme por qué mi preferencia es o no es una buena práctica?

    – Lansoriano

    25 de enero de 2021 a las 6:39

  • dentro de try catch, capturas la excepción donde quieras. Cuando tiene, por ejemplo: los mismos tipos de excepción que se lanzan en diferentes lugares, no podrá saber dónde se ha lanzado.

    – januszj

    25 de enero de 2021 a las 12:47

  • @janusz j: Gracias y ya veo. En otras palabras, si mi método de prueba tiene 0 líneas de código fuera del bloque try catch, ¿estaría bien?

    – Lansoriano

    26 de enero de 2021 a las 10:47

avatar de usuario
c_fabricante

tienes que usar @Test(expected=SomeException.class)? Cuando tenemos que afirmar el mensaje real de la excepción, esto es lo que hacemos.

@Test
public void myTestMethod()
{
  try
  {
    final Integer employeeId = null;
    new Employee(employeeId);
    fail("Should have thrown SomeException but did not!");
  }
  catch( final SomeException e )
  {
    final String msg = "Employee ID is null";
    assertEquals(msg, e.getMessage());
  }
}

En realidad, el mejor uso es con try/catch. ¿Por qué? Porque puedes controlar el lugar donde esperas la excepción.

Considere este ejemplo:

@Test (expected = RuntimeException.class)
public void someTest() {
   // test preparation
   // actual test
}

¿Qué sucede si un día se modifica el código y la preparación de la prueba genera una RuntimeException? En ese caso, la prueba real ni siquiera se prueba e incluso si no arroja ninguna excepción, la prueba pasará.

Por eso es mucho mejor usar try/catch que confiar en la anotación.

  • Lamentablemente, esta es mi respuesta también.

    – Sridhar Sarnobat

    22 de julio de 2016 a las 21:09

  • Las preocupaciones con respecto a los cambios de código se alivian al tener pequeños casos de prueba específicos de permutación. A veces es inevitable y tenemos que confiar en el método catch/try, pero si eso sucede con frecuencia, es probable que necesitemos revisar la forma en que escribimos nuestras funciones de casos de prueba.

    – luis.espinal

    4 oct 2016 a las 13:41

  • Eso es un problema con su prueba y/o código. NO espera una RuntimeException general, espera una excepción específica o, al menos, un mensaje específico.

    – DennisK

    16 de noviembre de 2016 a las 10:52


  • solía RuntimeException como ejemplo, reemplace esta excepción con cualquier otra excepción.

    – Krzysztof Cislo

    4 de diciembre de 2016 a las 10:27

avatar de usuario
usuario64141

Raystorm tenía una buena respuesta. Yo tampoco soy un gran fan de las Reglas. Hago algo similar, excepto que creo la siguiente clase de utilidad para mejorar la legibilidad y el uso, que es una de las grandes ventajas de las anotaciones en primer lugar.

Agregue esta clase de utilidad:

import org.junit.Assert;

public abstract class ExpectedRuntimeExceptionAsserter {

    private String expectedExceptionMessage;

    public ExpectedRuntimeExceptionAsserter(String expectedExceptionMessage) {
        this.expectedExceptionMessage = expectedExceptionMessage;
    }

    public final void run(){
        try{
            expectException();
            Assert.fail(String.format("Expected a RuntimeException '%s'", expectedExceptionMessage));
        } catch (RuntimeException e){
            Assert.assertEquals("RuntimeException caught, but unexpected message", expectedExceptionMessage, e.getMessage());
        }
    }

    protected abstract void expectException();

}

Luego, para mi prueba unitaria, todo lo que necesito es este código:

@Test
public void verifyAnonymousUserCantAccessPrivilegedResourceTest(){
    new ExpectedRuntimeExceptionAsserter("anonymous user can't access privileged resource"){
        @Override
        protected void expectException() {
            throw new RuntimeException("anonymous user can't access privileged resource");
        }
    }.run(); //passes test; expected exception is caught, and this @Test returns normally as "Passed"
}

  • Lamentablemente, esta es mi respuesta también.

    – Sridhar Sarnobat

    22 de julio de 2016 a las 21:09

  • Las preocupaciones con respecto a los cambios de código se alivian al tener pequeños casos de prueba específicos de permutación. A veces es inevitable y tenemos que confiar en el método catch/try, pero si eso sucede con frecuencia, es probable que necesitemos revisar la forma en que escribimos nuestras funciones de casos de prueba.

    – luis.espinal

    4 oct 2016 a las 13:41

  • Eso es un problema con su prueba y/o código. NO espera una RuntimeException general, espera una excepción específica o, al menos, un mensaje específico.

    – DennisK

    16 de noviembre de 2016 a las 10:52


  • solía RuntimeException como ejemplo, reemplace esta excepción con cualquier otra excepción.

    – Krzysztof Cislo

    4 de diciembre de 2016 a las 10:27

avatar de usuario
geeksusma

Nunca me gustó la forma de hacer valer las excepciones con Junit. Si utilizo el “esperado” en la anotación, parece, desde mi punto de vista, que estamos violando el patrón “dado, cuándo, entonces” porque “entonces” se coloca en la parte superior de la definición de la prueba.

Además, si usamos “@Rule”, tenemos que lidiar con tanto código repetitivo. Entonces, si puede instalar nuevas bibliotecas para sus pruebas, le sugiero que eche un vistazo a AssertJ (esa biblioteca ahora viene con SpringBoot)

Luego, una prueba que no viola los principios “dado/cuando/entonces”, y se realiza usando AssertJ para verificar:

1 – La excepción es lo que estamos esperando. 2 – También tiene un mensaje esperado

Se verá así:

 @Test
void should_throwIllegalUse_when_idNotGiven() {

    //when
    final Throwable raisedException = catchThrowable(() -> getUserDAO.byId(null));

    //then
    assertThat(raisedException).isInstanceOf(IllegalArgumentException.class)
            .hasMessageContaining("Id to fetch is mandatory");
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad