Search code examples
javajunittimeout

How do I apply my JUnit rules in a specific order?


We have a timeout enforced per-test via a test rule:

public abstract class BaseTestCase {
    @Rule
    public final Timeout timeout = new Timeout(60*1000);
}

Then our GUI tests obviously have to run on the EDT, so they have a test rule for that too:

public class TestSomeController extends BaseTestCase {
    @Rule
    public final RunOnEventDispatchThread runOnEventDispatchThread =
        new RunOnEventDispatchThread();

    @Test
    public void testStuff() { /* ... */ }
}

Both of these rules involves messing with the thread the test runs on. Timeout creates a new thread and then kills it N milliseconds later. RunOnEventDispatchThread runs a Runnable on the EDT and then waits for the task to complete.

Up to JUnit 4.10 this has been working fine, but we just upgraded to JUnit 4.11 and now it seems that the ordering of test rules has changed.

The current JUnit seems to be applying the rules in "logical" order, so it applies Timeout and then RunOnEventDispatchThread.

This means that RunOnEventDispatchThread ends up on the "outside" of the Statement "onion". So it runs the task on the EDT, but that task is now "spawn a new thread to run the test" - because it's spawning a new thread, the test fails, because Swing calls are now being called on the wrong thread.

Obviously JUnit 4.10 was running the test rules in another order. Is there a way to influence this?

Note that existing answers to this sort of question have all been addressed by using RuleChain. As far as I can tell, we cannot use RuleChain because that class demands both rules to be in the same class, and ours are not.


Solution

  • I am not very familiar with the rule ordering in JUnit and especially the RuleChain class. So I had a quick look at this class' documentation. The example code snippet seems to be your solution when being used in a template method sense.

    I could imagine that following mechanism will work:

    public abstract class BaseTestCase {
    
        @Rule
        public final RuleChain ruleChain = createRuleChain();
    
        private RuleChain createRuleChain() {
            return appendInnerRules(RuleChain.outerRule(createOuterRule()));
        }
    
        protected TestRule createOuterRule() {
            return new Timeout(60 * 1000);
        }
    
        protected RuleChain appendInnerRules(RuleChain ruleChain) {
            return ruleChain; // default behavior
        }
    
    }
    
    public final class TestSomeController extends BaseTestCase {
    
        protected RuleChain appendInnerRules(RuleChain ruleChain) {
            return ruleChain.around(new RunOnEventDispatchThread());
        }
    
        @Test
        public void testStuff() { /* ... */ }
    
    }