Skip to content

Deep dive into rules order with RuleSequence

Aleksei Tiurin edited this page Apr 1, 2021 · 2 revisions

Rules order

RuleSequence is a replacement of standart JUnit4 RuleChain class.

Lists of rules with different priority inside RuleSequence

There are 3 lists of rules:

  1. firstRules - will be executed firstly
  2. rules - will be executed normally after firstRules
  3. lastRules - will be executed last

The rules provided in constructor will be added to normal rules.

An order of rules execution depends on 2 things:

  • the priority of rules list
  • the order these rules were added to RuleSequence.
RuleSequence(normalRule)
    .addLast(rule1, rule2)
    .add(rule3, rule4)
    .addFirst(rule5, rule6)

In case above rules will be executed in following order: rule5, rule6, normalRule, rule3, rule4, rule1, rule2

interface RuleSequenceTearDown

I didn't find this info in any junit doc or anywhere else. But it's really important to understand.

Lets look at some example of how standart junit rules invoke their methods. We take for example TestWatcher subclass.

class MyTestWatcherRule(val name: String): TestWatcher() {
    override fun starting(description: Description?) {
        Log.debug("$name starting")
    }
    override fun finished(description: Description?) {
        Log.debug("$name finished")
    }
}

Let's make a little experiment with junit RuleChain.

class TestWatcherSampleTest {
    @get:Rule
    val ruleChain = RuleChain
            .outerRule(MyTestWatcherRule("rule 1"))
            .around(MyTestWatcherRule("rule 2"))
    @Test
    fun someTest(){
        Log.debug("test")
    }
}

We execute test and open log.

rule 1 starting
rule 2 starting
test
rule 2 finished
rule 1 finished

As you can see finished method is execudet in reverse order. We don't wanna explain why it works so strange. It actually can blow our mind. (We realise that it's some kind of junit concept but it's really inconvenient to use)

To tell you the truth RuleSequence works almost the same way. But almost is really important word here.

If we just replace RuleChain to RuleSequence the order of methods will be the same. And it even broke the logic of internal lists priority for such methods. But we found the solution in RuleSequenceTearDown interface. Let's mark that our custom class implement this interface.

class MyTestWatcherRule(val name: String): TestWatcher(), RuleSequenceTearDown {
    //there is no change
} 

Replace RuleChain to RuleSequence

class TestWatcherSampleTest {
    @get:Rule
    val ruleChain = RuleSequence()
            .add(MyTestWatcherRule("rule 1"))
            .add(MyTestWatcherRule("rule 2"))
    @Test
    fun someTest(){
        Log.debug("test")
    }
}

And finally the order becomes proper.

rule 1 starting
rule 2 starting
test
rule 1 finished
rule 2 finished

As you might have guessed the TearDownRule also implements RuleSequenceTearDown interface and works in the same way.