Using JUnit Rules

JUnit rules allows the definition of work to be done before and after a test case execution. Than means before either the test execution and also all the @before annotated methods. This facility allows creation of more powerful and cleaner tests. It is a concept similar to JUnitRunners but with the advantage that we can combine multiple rules.

There are a couple of useful rules already implemented in JUnit. For example the TemporaryFolderRule allows creation of files and folders that are guaranteed to be deleted when the test case finishes. A complete rule list is available in https://github.com/junit-team/junit/wiki/Rules.

If we want to create our custom rules we can simply do:

public class MyTestRule implements TestRule {
	public Statement apply(final Statement base, Description description) {
		return new Statement() {
			@Override
			public void evaluate() throws Throwable {
				System.out.println("before");
				try {
					base.evaluate();
				} finally {
					System.out.println("after");
				}
			}
		};
	}
}

And we will use it in our tests via the @Rule annotation.

public class MyTest {
	@Rule
	public MyTestRule myTestRule = new MyTestRule();
 
	@Test
	public void testSanity() throws Exception {
		System.out.println("test execution");
	}
}

Running this test case will print:

before
test execution
after

that proves that the rule interceptor is working as expected.

A real example could be a rule to take snapshoots when a selenium ui tests fails, for example:

public class ScreenshotRule implements TestRule {
	public Statement apply(final Statement base, final Description description) {
		return new Statement() {
			@Override
			public void evaluate() throws Throwable {
				try {
					base.evaluate();
				} catch (Throwable t) {
					takeScreenshot();
					throw t; // Re-throw to allow the failure to be reported to JUnit
				}
			}
			private void takeScreenshot(){
                                WebDriver driver = new FirefoxDriver();
				File imageFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
				String failureImageFileName = "testfailureimage.png";
				File failureImageFile = new File(failureImageFileName);
				FileUtils.moveFile(imageFile, failureImageFile);
			}
		};
	}
}
public class MySeleniumTest {
	@Rule
	public ScreenshotRule screenshotRule = new ScreenshotRule();
 
	@Test
	public void testThatFails() {
		WebDriver driver = new FirefoxDriver();
		driver.get("http://www.google.com");
		driver.findElement(By.id("non_existing_element"));
	}
}

Using rules we can reduce the boilerplate code in our tests, it’s especially useful for Integration Tests or Automation Tests that usually share a complex context or environment set up previous the test execution.

Other interesting use cases of rules are to use them as a Runners replacement, when we want to combine more than one Runner. You can see an example for SpringJUnit4ClassRunner or MockitoJUnitRunner in http://www.alexecollins.com/content/tutorial-junit-rule/

An other interesting point is to combine Rules with custom annotations.

Ordering Rules
We must be careful using multiple rules because the order of excution is not assured by default. To illustrate that problem we can use the next custom rule:

public class LoggingRule implements TestRule {
	private String name;
	public LoggingRule(String name) {
		this.name = name;
	}
	public Statement apply(final Statement base, Description description) {
		return new Statement() {
			@Override
			public void evaluate() throws Throwable {
				try {
					System.out.println("starting " + name);
					base.evaluate();
				} finally {
					System.out.println("finished " + name);
				}
			}
		};
	}
}

And create a little test with three rules.

public class RulesOrderTest {
	
	@Rule public LoggingRule rule1 = new LoggingRule("rule 1");
	@Rule public LoggingRule rule2 = new LoggingRule("rule 2");
	@Rule public LoggingRule rule3 = new LoggingRule("rule 3");

	@Test
	public void test() {
	}
}

Running this test case will print:

starting rule 3
starting rule 2
starting rule 1
finished rule 1
finished rule 2
finished rule 3

But that order could be different if we execute it in other java VM.
If we want to assure the ordering of the rules execution we should use the RuleChain test rule. The order in the chain will be the order of the rules execution.

public class RulesOrderTest {
		
	@Rule 
	public TestRule chain = RuleChain
                           .outerRule(new LoggingRule("outer rule"))
                           .around(new LoggingRule("middle rule"))
                           .around(new LoggingRule("inner rule"));
	@Test
	public void test() {
	}
}

Now it will print:

starting outer rule
starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule

Documentation:
https://github.com/junit-team/junit/wiki/Rules
http://www.alexecollins.com/content/tutorial-junit-rule/
http://www.codeaffine.com/2012/09/24/junit-rules/
http://junit.org/javadoc/latest/org/junit/rules/RuleChain.html

Advertisements