Detect Failure or Error of Junit Test in @After method
I don't know any easy or elegant way to detect the failure of a Junit test in an @After method.
If it is possible to use a TestRule instead of an @After method, one possibility to do it is using two chained TestRules, using a TestWatcher as the inner rule.
Example:
package org.example;
import static org.junit.Assert.fail;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
public class ExampleTest {
private String name = "";
private boolean failed;
@Rule
public TestRule afterWithFailedInformation = RuleChain
.outerRule(new ExternalResource(){
@Override
protected void after() {
System.out.println("Test "+name+" "+(failed?"failed":"finished")+".");
}
})
.around(new TestWatcher(){
@Override
protected void finished(Description description) {
name = description.getDisplayName();
}
@Override
protected void failed(Throwable e, Description description) {
failed = true;
}
})
;
@Test
public void testSomething(){
fail();
}
@Test
public void testSomethingElse(){
}
}
I extend dsaff's answer to solve the problem that a TestRule
can not execute some code snipped between the execution of the test-method and the after-method. So with a simple MethodRule
one can not use this rule to provide a success flag that is use in the @After
annotated methods.
My idea is a hack! Anyway, it is to use a TestRule
(extends TestWatcher
). A TestRule
will get knowledge about failed or success of a test. My TestRule
will then scan the class for all Methods annotated with my new AfterHack
annotations and invoke that methods with a success flag.
AfterHack
annotation
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface AfterHack {}
AfterHackRule
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
public class AfterHackRule extends TestWatcher {
private Object testClassInstance;
public AfterHackRule(final Object testClassInstance) {
this.testClassInstance = testClassInstance;
}
protected void succeeded(Description description) {
invokeAfterHackMethods(true);
}
protected void failed(Throwable e, Description description) {
invokeAfterHackMethods(false);
}
public void invokeAfterHackMethods(boolean successFlag) {
for (Method afterHackMethod :
this.getAfterHackMethods(this.testClassInstance.getClass())) {
try {
afterHackMethod.invoke(this.testClassInstance, successFlag);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new RuntimeException("error while invoking afterHackMethod "
+ afterHackMethod);
}
}
}
private List<Method> getAfterHackMethods(Class<?> testClass) {
List<Method> results = new ArrayList<>();
for (Method method : testClass.getMethods()) {
if (method.isAnnotationPresent(AfterHack.class)) {
results.add(method);
}
}
return results;
}
}
Usage:
public class DemoTest {
@Rule
public AfterHackRule afterHackRule = new AfterHackRule(this);
@AfterHack
public void after(boolean success) {
System.out.println("afterHack:" + success);
}
@Test
public void demofails() {
Assert.fail();
}
@Test
public void demoSucceeds() {}
}
BTW:
- 1) Hopefully there is a better solution in Junit5
- 2) The better way is to use the TestWatcher Rule instead of the @Before and @After Method at all (that is the way I read dsaff's answer)
@see
If you are lucky enough to be using JUnit 4.9 or later, TestWatcher
will do exactly what you want.
Share and Enjoy!