when try test expectedexception expected example catch java exception junit

java - try - junit when throw exception



JUnit: ¿Es posible ''esperar'' una excepción envuelta? (7)

Sé que uno puede definir una excepción ''esperada'' en JUnit, haciendo:

@Test(expect=MyException.class) public void someMethod() { ... }

Pero, ¿qué ocurre si siempre se lanza la misma excepción, pero con diferentes causas ''anidadas''?

¿Alguna sugerencia?


A partir de JUnit 4.11 puede usar el método expectCause() la regla ExpectedException :

import static org.hamcrest.CoreMatchers.*; // ... @Rule public ExpectedException expectedException = ExpectedException.none(); @Test public void throwsNestedException() throws Exception { expectedException.expectCause(isA(SomeNestedException.class)); throw new ParentException("foo", new SomeNestedException("bar")); }


Escribí una pequeña extensión JUnit para ese propósito. Una función auxiliar estática toma un cuerpo de función y una matriz de excepciones esperadas:

import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Arrays; public class AssertExt { public static interface Runnable { void run() throws Exception; } public static void assertExpectedExceptionCause( Runnable runnable, @SuppressWarnings("unchecked") Class[] expectedExceptions ) { boolean thrown = false; try { runnable.run(); } catch( Throwable throwable ) { final Throwable cause = throwable.getCause(); if( null != cause ) { assertTrue( Arrays.asList( expectedExceptions ).contains( cause.getClass() ) ); thrown = true; } } if( !thrown ) { fail( "Expected exception not thrown or thrown exception had no cause!" ); } } }

Ahora puede verificar las excepciones anidadas esperadas como estas:

import static AssertExt.assertExpectedExceptionCause; import org.junit.Test; public class TestExample { @Test public void testExpectedExceptionCauses() { assertExpectedExceptionCause( new AssertExt.Runnable(){ public void run() throws Exception { throw new Exception( new NullPointerException() ); } }, new Class[]{ NullPointerException.class } ); } }

Esto le ahorra escribir el mismo código de placa de caldera una y otra vez.


La sintaxis más concisa es proporcionada por catch-exception :

import static com.googlecode.catchexception.CatchException.*; catchException(myObj).doSomethingNasty(); assertTrue(caughtException().getCause() instanceof MyException);


Puede ajustar el código de prueba en un bloque try / catch, atrapar la excepción lanzada, verificar la causa interna, registrar / afirmar / lo que sea, y luego volver a lanzar la excepción (si lo desea).


Puede crear un Matcher para excepciones. Esto funciona incluso cuando está utilizando otro corredor de prueba como Arquillian ''s @RunWith(Arquillian.class) por lo que no puede usar el @RunWith(ExtendedTestRunner.class) sugerido anteriormente.

Aquí hay un ejemplo simple:

public class ExceptionMatcher extends BaseMatcher<Object> { private Class<? extends Throwable>[] classes; // @SafeVarargs // <-- Suppress warning in Java 7. This usage is safe. public ExceptionMatcher(Class<? extends Throwable>... classes) { this.classes = classes; } @Override public boolean matches(Object item) { for (Class<? extends Throwable> klass : classes) { if (! klass.isInstance(item)) { return false; } item = ((Throwable) item).getCause(); } return true; } @Override public void describeTo(Description descr) { descr.appendText("unexpected exception"); } }

Entonces @Rule con @Rule y ExpectedException siguiente manera:

@Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testSomething() { thrown.expect(new ExceptionMatcher(IllegalArgumentException.class, IllegalStateException.class)); throw new IllegalArgumentException("foo", new IllegalStateException("bar")); }

Agregado por Craig Ringer en edición de 2012: una versión mejorada y más confiable:

  • Uso básico sin cambios desde arriba
  • Puede pasar el primer argumento boolean rethrow argumento para lanzar una excepción sin igual. Eso conserva el seguimiento de la pila de las excepciones anidadas para una depuración más fácil.
  • Utiliza Apache Commons Lang ExceptionUtils para manejar bucles de causa y para manejar el anidamiento de excepción no estándar utilizado por algunas clases de excepciones comunes.
  • Autodescripción incluye excepciones aceptadas
  • La autodescripción en la falla incluye una pila de la causa de la excepción encontrada
  • Manejar la advertencia de Java 7. Elimine @SaveVarargs en versiones anteriores.

Código completo:

import org.apache.commons.lang3.exception.ExceptionUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; public class ExceptionMatcher extends BaseMatcher<Object> { private Class<? extends Throwable>[] acceptedClasses; private Throwable[] nestedExceptions; private final boolean rethrow; @SafeVarargs public ExceptionMatcher(Class<? extends Throwable>... classes) { this(false, classes); } @SafeVarargs public ExceptionMatcher(boolean rethrow, Class<? extends Throwable>... classes) { this.rethrow = rethrow; this.acceptedClasses = classes; } @Override public boolean matches(Object item) { nestedExceptions = ExceptionUtils.getThrowables((Throwable)item); for (Class<? extends Throwable> acceptedClass : acceptedClasses) { for (Throwable nestedException : nestedExceptions) { if (acceptedClass.isInstance(nestedException)) { return true; } } } if (rethrow) { throw new AssertionError(buildDescription(), (Throwable)item); } return false; } private String buildDescription() { StringBuilder sb = new StringBuilder(); sb.append("Unexpected exception. Acceptable (possibly nested) exceptions are:"); for (Class<? extends Throwable> klass : acceptedClasses) { sb.append("/n "); sb.append(klass.toString()); } if (nestedExceptions != null) { sb.append("/nNested exceptions found were:"); for (Throwable nestedException : nestedExceptions) { sb.append("/n "); sb.append(nestedException.getClass().toString()); } } return sb.toString(); } @Override public void describeTo(Description description) { description.appendText(buildDescription()); } }

Salida típica:

java.lang.AssertionError: Expected: Unexpected exception. Acceptable (possibly nested) exceptions are: class some.application.Exception Nested exceptions found were: class javax.ejb.EJBTransactionRolledbackException class javax.persistence.NoResultException got: <javax.ejb.EJBTransactionRolledbackException: getSingleResult() did not retrieve any entities.>


Si está utilizando la última versión de JUnit, puede extender el corrector de prueba predeterminado para que lo haga por usted (sin tener que ajustar cada uno de sus métodos en un bloque try / catch).

ExtendedTestRunner.java - Nuevo corredor de prueba:

public class ExtendedTestRunner extends BlockJUnit4ClassRunner { public ExtendedTestRunner( Class<?> clazz ) throws InitializationError { super( clazz ); } @Override protected Statement possiblyExpectingExceptions( FrameworkMethod method, Object test, Statement next ) { ExtendedTest annotation = method.getAnnotation( ExtendedTest.class ); return expectsCauseException( annotation ) ? new ExpectCauseException( next, getExpectedCauseException( annotation ) ) : super.possiblyExpectingExceptions( method, test, next ); } @Override protected List<FrameworkMethod> computeTestMethods() { Set<FrameworkMethod> testMethods = new HashSet<FrameworkMethod>( super.computeTestMethods() ); testMethods.addAll( getTestClass().getAnnotatedMethods( ExtendedTest.class ) ); return testMethods; } @Override protected void validateTestMethods( List<Throwable> errors ) { super.validateTestMethods( errors ); validatePublicVoidNoArgMethods( ExtendedTest.class, false, errors ); } private Class<? extends Throwable> getExpectedCauseException( ExtendedTest annotation ) { if (annotation == null || annotation.expectedCause() == ExtendedTest.None.class) return null; else return annotation.expectedCause(); } private boolean expectsCauseException( ExtendedTest annotation) { return getExpectedCauseException(annotation) != null; } }

ExtendedTest.java - anotación para marcar los métodos de prueba con:

@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface ExtendedTest { /** * Default empty exception */ static class None extends Throwable { private static final long serialVersionUID= 1L; private None() { } } Class<? extends Throwable> expectedCause() default None.class; }

ExpectCauseException.java - nueva instrucción JUnit:

public class ExpectCauseException extends Statement { private Statement fNext; private final Class<? extends Throwable> fExpected; public ExpectCauseException( Statement next, Class<? extends Throwable> expected ) { fNext= next; fExpected= expected; } @Override public void evaluate() throws Exception { boolean complete = false; try { fNext.evaluate(); complete = true; } catch (Throwable e) { if ( e.getCause() == null || !fExpected.isAssignableFrom( e.getCause().getClass() ) ) { String message = "Unexpected exception cause, expected<" + fExpected.getName() + "> but was<" + ( e.getCause() == null ? "none" : e.getCause().getClass().getName() ) + ">"; throw new Exception(message, e); } } if (complete) throw new AssertionError( "Expected exception cause: " + fExpected.getName()); } }

Uso:

@RunWith( ExtendedTestRunner.class ) public class MyTests { @ExtendedTest( expectedCause = MyException.class ) public void someMethod() { throw new RuntimeException( new MyException() ); } }


Siempre puedes hacerlo manualmente:

@Test public void someMethod() { try{ ... all your code } catch (Exception e){ // check your nested clauses if(e.getCause() instanceof FooException){ // pass } else { Assert.fail("unexpected exception"); } }