utiliza - testing en java




¿Cómo afirma que se arroja una cierta excepción en las pruebas de JUnit 4? (20)

¿Cómo puedo usar JUnit4 de forma idiomática para probar que algún código produce una excepción?

Aunque 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 torpe y mucho más en el espíritu de JUnit para este tipo de situaciones.


Solución de Java 8

Si desea una solución que:

  • Utiliza Java 8 lambdas
  • No depende de ninguna magia JUnit.
  • Le permite verificar múltiples excepciones dentro de un solo método de prueba
  • Comprueba si una excepción es lanzada 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 )

Úsalo de la siguiente manera:

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

Solución JUnit 5

@Test
void testFooThrowsIndexOutOfBoundsException() {    
  Throwable exception = expectThrows( IndexOutOfBoundsException.class, foo::doStuff );

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

Más información sobre JUnit 5 en improved


¿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 en su lugar obtuvo un puntero nulo) 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);
}

Ahora que se lanzó JUnit 5, la mejor opción es usar Assertions.assertThrows() (consulte la Guía del usuario de Junit 5 ).

Aquí hay un ejemplo que verifica que se lanza una excepción, y usa Truth 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. Incorporado en JUnit
  2. Recibirá un mensaje de excepción útil si el código en la lambda no lanza una excepción, y un stacktrace si lanza 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 es necesario que incluya la excepción esperada en la cláusula de throws
  7. Puede utilizar el marco de aserción de su elección para hacer afirmaciones sobre la excepción detectada

Se agregará un método similar a org.junit Assert en JUnit 4.13.


En Junit, hay cuatro maneras de probar la excepción.

  • para junit4.x, use el atributo 'esperado' opcional de la anonació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 utilizar la forma clásica de prueba / captura ampliamente utilizada 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 puede 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 utilizan cuando desea un mensaje de excepción de prueba adicional
    • Si usas Junit 3, entonces se prefiere el tercero.
    • Si te gusta Junit 5, entonces te gustaría el 4to.
  • para obtener más información, puede leer github.com/junit-team/junit/wiki/Exception-testing y la guía del usuario de junit5 para obtener más información.


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

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

assertTrue puede ser un poco fuerte para algunas personas, por lo que assertThat(e.getMessage(), containsString("the message"); puede ser preferible.


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



También puedes hacer esto:

@Test
public void testFooThrowsIndexOutOfBoundsException() {
    try {
        foo.doStuff();
        assert false;
    } catch (IndexOutOfBoundsException e) {
        assert true;
    }
}

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

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

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

Aplicar juicio.


Actualización: JUnit5 tiene una mejora para pruebas de excepciones: assertThrows .

El siguiente ejemplo es de: Junit 5 User Guide

 @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 lanza una excepción. También he discutido las opciones a continuación en mi publicación Cómo escribir excelentes pruebas unitarias con JUnit

Establezca el parámetro expected @Test(expected = FileNotFoundException.class) .

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

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

}

Pruebas con Regla de ExpectedException .

@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 el wiki JUnit4 para las pruebas de excepciones y bad.robot - Expecting Exceptions JUnit Rule .


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 la Regla ExpectedException :

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 se lanza antes de foo.doStuff()

Ver este artículo para más detalles.


Con Java 8 puede crear un método tomando un código para verificar y esperar una excepción 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:

  • sin depender de ninguna biblioteca
  • control localizado: más preciso y permite tener múltiples aseveraciones como esta dentro de una prueba si es necesario
  • fácil de usar

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

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


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

  1. El tipo de excepción arrojado
  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");
    }

}

La solución 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 aseveraciones de prueba en lugar de esperar que Thowable a nivel de método use la @Test(expected = IndexOutOfBoundsException.class)solución.


Mi solución utilizando Java 8 lambdas:

public static <T extends Throwable> T assertThrows(Class<T> expected, ThrowingRunnable action) throws Throwable {
    try {
        action.run();
        Assert.fail("Did not throw expected " + expected.getSimpleName());
        return null; // never actually
    } catch (Throwable actual) {
        if (!expected.isAssignableFrom(actual.getClass())) { // runtime '!(actual instanceof expected)'
            System.err.println("Threw " + actual.getClass().getSimpleName() 
                               + ", which is not a subtype of expected " 
                               + expected.getSimpleName());
            throw actual; // throw the unexpected Throwable for maximum transparency
        } else {
            return (T) actual; // return the expected Throwable for further examination
        }
    }
}

Tienes que definir un FunctionalInterface, porque Runnableno declara lo requerido throws.

@FunctionalInterface
public interface ThrowingRunnable {
    void run() throws Throwable;
}

El método se puede utilizar de la siguiente manera:

class CustomException extends Exception {
    public final String message;
    public CustomException(final String message) { this.message = message;}
}
CustomException e = assertThrows(CustomException.class, () -> {
    throw new CustomException("Lorem Ipsum");
});
assertEquals("Lorem Ipsum", e.message);

Solo crea un Matcher que pueda ser activado y desactivado de la siguiente 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:

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

ExceptionMatcher exMatch = new ExceptionMatcher(MyException.class);
exception.expect(exMatch);
someObject.somethingThatThrowsMyException();
exMatch.off();

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 la siguiente es para hacer valer 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"});
}

En mi caso, siempre recibo RuntimeException de db, pero los mensajes difieren. Y la excepción debe ser manejada respectivamente. Aquí es cómo 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));
}

La respuesta más flexible y elegante para Junit 4 que encontré en el blog Mkyong . Tiene la flexibilidad de try/catchutilizar la @Ruleanotación. Me gusta este enfoque porque puede 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("");

    }

}




assert