Hello!
I know that one can define an 'expected' exception in JUnit, doing:
@Test(expect=MyException.class)
public void someMethod() { ... }
But what if there is always same exception thrown, but with different 'nested' causes.
Any suggestions?
Hello!
I know that one can define an 'expected' exception in JUnit, doing:
@Test(expect=MyException.class)
public void someMethod() { ... }
But what if there is always same exception thrown, but with different 'nested' causes.
Any suggestions?
You could wrap the testing code in a try / catch block, catch the thrown exception, check the internal cause, log / assert / whatever, and then rethrow the exception (if desired).
You could always do it manually:
@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");
}
}
I wrote a little JUnit extension for that purpose. A static helper function takes a function body and an array of expected exceptions:
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!" );
}
}
}
You can now check for expected nested exceptions like so:
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 } );
}
}
This saves you writing the same boiler plate code again and again.
If you're using the latest version of JUnit you can extend the default test runner to handle this for you (without having to wrap each of your methods in a try/catch block)
ExtendedTestRunner.java - New test runner:
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()
{
List<FrameworkMethod> testMethods = new ArrayList<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 - annotation to mark test methods with:
@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 - new JUnit Statement:
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());
}
}
Usage:
@RunWith( ExtendedTestRunner.class )
public class MyTests
{
@ExtendedTest( expectedCause = MyException.class )
public void someMethod()
{
throw new RuntimeException( new MyException() );
}
}