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!

Tags:

Java

Junit