Aggregating JUnit Rules with dependencies between one another
Asked Answered
B

2

12

In more complex unit tests, I often require a certain set of Rules to be present. Some of these Rules have dependencies to another. As the ordering is relevant, I use RuleChains for that. All good so far.

This however is duplicated in most tests (with the occasional additional rule being used). Not only does this duplication feel unnecessary and cumbersome to repeat, it also needs to be adjusted in many places, when an additional Rule should be integrated.

What I would like to have is a Rule of Rules, i.e. a (predefined) Rule that contains or aggregates other (application & test specific) Rules.

I'll give an example of how this currently looks like:

public LoggingRule logRule = new LogRule();
public ConfigurationRule configurationRule = new ConfigurationRule();
public DatabaseConnectionRule dbRule = new DatabaseConnectionRule();
public ApplicationSpecificRule appRule = new ApplicationSpecificRule();

@Rule
RuleChain chain = RuleChain.outerRule(logRule)
                           .around(configurationRule)
                           .around(dbRule)
                           .around(appRule);

Assume that the given Rules depend on each other, e.g. the ApplicationSpecificRule requires that the DatabaseConnectionRule is executed first in order to establish a connection, the ConfigurationRule has initialized an empty configuration, etc. Also assume that for this (rather complex test) all rules are actually required.

The only solution I could come up with so far is to create factory methods that return a predefined RuleChain:

public class ApplicationSpecificRule extends ExternalResource
{
    public static RuleChain basicSet()
    {
         return RuleChain.outerRule(new LogRule())
                         .around(new ConfigurationRule())
                         .around(new DatabaseConnectionRule())
                         .around(new ApplicationSpecificRule());
    }
}

In a test this can then be used as follows:

@Rule
RuleChain chain = ApplicationSpecificRule.basicSet();

With that the duplication is removed and additional Rules can easily be integrated. One could even add test-specific Rules to that RuleChain. However one can't access the contained Rules when they are required for additional setup (assume you need the ApplicationSpecificRule in order to create some domain object, etc.).

Ideally this would be extended to also support using other predefined sets, e.g. an advandancedSet that builds on top of the basicSet of Rules.

Can this be somehow simplified? Is it a good idea in the first place or am I somehow misusing Rules? Would it help to restructure the tests? Thoughts?

Boxing answered 22/3, 2015 at 14:46 Comment(0)
F
8

The TestRule interface has only one method, so it's quite easy can define your own custom rule that delegates to a RuleChain and keeps references to the other rules:

public class BasicRuleChain implements TestRule {
  private final RuleChain delegate;
  private final DatabaseConnectionRule databaseConnectionRule
      = new DatabaseConnectionRule();

  public BasicRuleChain() {
    delegate = RuleChain.outerRule(new LogRule())
        .around(new ConfigurationRule())
        .around(databaseConnectionRule)
        .around(new ApplicationSpecificRule());
  }

  @Override
  public Statement apply(Statement base, Description description) {
    return delegate.apply(base, description
  }

  public Connection getConnection() {
    return databaseConnectionRule.getConnection();
  }
}
Flowerpot answered 23/3, 2015 at 4:45 Comment(1)
Yes, this is a good idea. I'll probably not have my RuleChain provide certain methods from the internally used Rules though, but make the Rules themselves accessible. This way I'll not have to extend the BasicRuleChain for every additional use case.Boxing
U
2

Doesn't get much simpler then that, does it? The only thing that would make it even simpler is to just use an instance instead of a factory, since you don't need fresh instances all the time.

Unitarianism answered 22/3, 2015 at 16:2 Comment(2)
Good suggestion about the instance vs. factory. How would you go about accessing the contained Rules though?Boxing
You definitely do not want to use an instance. Some rules have internal state that shouldn't be shared with other testsFlowerpot

© 2022 - 2024 — McMap. All rights reserved.