3

I'm trying to create test automation suite using JUnit. For all the tests, I want to create a Rule, for this I have created an Interface and placed the Rule inside it. Any tests that I want to run will have to implent that interface. It didn't throw any compiler error, however, when my Test class implements that interface, this doesn't seem to work. Following is the Interface I created.

public interface IBaseTest {
    @Rule
    public TestRule test = new TestWatcher(){
        @Override
        protected void starting(Description description)
        {
            System.out.println("Starting Test: " + description);
        }
    };
}

Alternatively, I could have created the above as a class and extend all my test classes from that class, I tried that and it worked perfectly, however this would prevent me from extending my test methods from any other class.

Is there a way for me to create rules that will be applicable for all my tests without extending from a base class?

Buddha
  • 4,339
  • 2
  • 27
  • 51

1 Answers1

1

Yes there is a way that I am aware of but it will make you write some extra code.

First, the reason that JUnit is ignoring your TestRule is because it is declared on an interface hence static (and final).

To overcome this issue one will need to write a custom runner like this:

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.InitializationError;

public final class MyRunner extends BlockJUnit4ClassRunner {

    public MyRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected List<TestRule> getTestRules(Object target) {
        List<TestRule> testRules = super.getTestRules(target);
        testRules.addAll(getStaticFieldTestRules(target));
        return testRules;
    }

    private List<TestRule> getStaticFieldTestRules(Object target) {
        List<TestRule> testRules = new ArrayList<>();
        Class<?> clazz = target.getClass();
        for (Field f : clazz.getFields()) {
            if ((f.getModifiers() & Modifier.STATIC) != 0) {
                if (f.isAnnotationPresent(Rule.class)) {
                    try {
                        testRules.add((TestRule) f.get(target));
                    } catch (IllegalArgumentException | IllegalAccessException e) {
                        throw new IllegalStateException(e);
                    }
                }
            }
        }
        return testRules;
    }
}

Finally, annotate your test class to run with the new custom runner and all is as you expect...

import org.junit.runner.RunWith;

@RunWith(MyRunner.class)
public class Test implements IBaseTest {

    @org.junit.Test
    public void testName1() throws Exception {
    }

    @org.junit.Test
    public void testName2() throws Exception {

    }

}
shlomi33
  • 1,458
  • 8
  • 9
  • Awesome. Little more help. I have created a custom runner using Suite. Is there a way to acheive the same by extending Suite Runner? I couldn't find any relevant method. – Buddha Aug 03 '14 at 13:11
  • You mean to avoid the need to annotate the custom runner on each test class? – shlomi33 Aug 03 '14 at 13:30
  • Yes. Reason being, we need to created several suites from a bunch of test classes. Same test classes may be repeated. – Buddha Aug 03 '14 at 13:37
  • I can't find either. Perhaps the authors did that intentionally, separating the 2 heirarchies of the ParentRunner to BlockJUnit4ClassRunner and Suite. So Suite is only aggregating tests and the BlockJUnit4ClassRunner extensions are the ones that are in charge of the test running strategy. I would stick to adding the runner to the test classes. Having said that, there still might be a possible way to integrate all to a Suite runner but currently I am unaware of it. – shlomi33 Aug 03 '14 at 14:06
  • Looks like it. Thanks for the info anyway. – Buddha Aug 03 '14 at 16:53