JUnit Rules – All Tactics with Examples!

JUnit Rules allow you to write code to do some before and after work. Thus, you don’t repeat to write the same code in various test classes. They are very useful to add more functionalities to all test methods in a test class. You can extend or reuse provided rules or write your own custom rules.

The base rules are listed below and you can find details about them on https://github.com/junit-team/junit/wiki/Rules web page.

  • TemporaryFolder Rule
  • ExternalResource Rules
  • ErrorCollector Rule
  • Verifier Rule
  • TestWatchman/TestWatcher Rules
  • TestName Rule
  • Timeout Rule
  • ExpectedException Rules
  • ClassRule
  • RuleChain
  • Custom Rules

In JUnit github link, you can find all details of each rule. Also, their core mechanism and flow are described in this article very well. I want to demonstrate the usage of some rules with examples.

Temporary Folder Rule

The TemporaryFolder Rule allows you to create files and folders. These files are folders that are deleted whether the test passes or fails when the test method finishes. By default, no exception is thrown if resources cannot be deleted. So, if you need to run a test that needs a temporary file or folder then you can use this rule as shown below.

import static org.junit.Assert.assertTrue;

import java.io.File;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class TemporaryFolderRule {
    @Rule public TemporaryFolder tempFolder = new TemporaryFolder();

    @Test public void testFile() throws Exception {
        File testFolder = tempFolder.newFolder("TestFolder");
        File testFile = tempFolder.newFile("test.txt");
        assertTrue(testFolder.exists());
        assertTrue(testFile.exists());
        //Do something else...
    }
}

Timeout Rule

The Timeout Rule applies the same timeout to all test methods in a class.

import java.util.concurrent.TimeUnit;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;

public class TimeoutRule {
    @Rule
    public Timeout timeout = new Timeout(2, TimeUnit.SECONDS);

    @Test
    public void testA() throws Exception {
        Thread.sleep(1000);
    }

    @Test
    public void testB() throws Exception {
        Thread.sleep(3000);
    }
}

ExpectedException Rules

The ExpectedException Rule allows having more control over expected exception types and messages.

public class ExpectedExceptionRule {
    @Rule
    public ExpectedException thrown = ExpectedException.none();

    @Test
    public void throwsNullPointerException() {
        thrown.expect(NullPointerException.class);
        throw new NullPointerException();
    }

    @Test
    public void throwsNullPointerExceptionWithMessage() {
        thrown.expect(NullPointerException.class);
        thrown.expectMessage("Null Pointer Problem!");
        throw new NullPointerException("Null Pointer Problem!");
    }

    //The new way of assertion Exceptions after 4.13 version of JUnit 4.
    //ExpectedException.none() is deprecated instead of this you can use Assert.assertThrows
    @Test
    public void throwsNullPointerExceptionNew() {
        Assert.assertThrows(NumberFormatException.class, () -> Integer.parseInt("Hello"));
        Assert.assertThrows(IllegalArgumentException.class, () -> Integer.parseInt("Hello"));
    }
}

Error Collector Rule

The ErrorCollector Rule allows execution of a test to continue after the first problem is found. It collects all the errors and reports them all at once.

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;

public class ErrorCollectorRule {

    @Rule
    public ErrorCollector collector = new ErrorCollector();

    @Test
    public void example() {
        collector.addError(new Throwable("First Error!"));
        collector.addError(new Throwable("Second Error!"));

        collector.checkThat(5, is(8)); //First Error
        collector.checkThat(5, is(not(8))); //Passed
        collector.checkThat(5, is(equalTo(9))); //Second Error
    }
}

Output:

Verifier Rule

 The verifier rule does a verification check and if it is failed, the test is finished with a failing result. You can write your custom verification logic with Verifier Rule.

import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Verifier;

public class VerifierRule {
    private List<String> errorLog = new ArrayList<>();

    @Rule
    public Verifier verifier = new Verifier() {
        //After each method perform this check.
        @Override public void verify() {
            assertTrue("Error Log is not Empty!", errorLog.isEmpty());
        }
    };

    @Test
    public void testWritesErrorLog() {
        //...
        errorLog.add("There is an error!");
    }
}

Output:

verifier_rule

TestWatchman/TestWatcher Rules

TestWatcher (and the deprecated TestWatchman) are classes for Rules and they watch test methods and write log for each passing and failing test. You can keep an eye on the tests with TestWatcher Rule.

import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.MethodSorters;
import org.junit.runners.model.Statement;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestWatcherRule {
    private static String watchedLog = null + "\n";

    @Rule
    public TestRule watchman = new TestWatcher() {
        @Override
        public Statement apply(Statement base, Description description) {
            return super.apply(base, description);
        }

        @Override
        protected void succeeded(Description description) {
            watchedLog += description.getDisplayName() + " " + "success!\n";
            System.out.println("Succeed! Watchlog:\n" + watchedLog);
        }

        @Override
        protected void failed(Throwable e, Description description) {
            watchedLog += description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n";
            System.out.println("Failed! Watchlog:\n" + watchedLog);
        }

        @Override
        protected void starting(Description description) {
            super.starting(description);
            System.out.println("Starting test! Watchlog:\n" + watchedLog);
        }

        @Override
        protected void finished(Description description) {
            super.finished(description);
            System.out.println("Test finished! Watchlog:\n" + watchedLog
                + "\n------------------------------------\n");
        }
    };

    @Test
    public void T1_succeeds() {
        Assert.assertEquals(5, 5);
    }

    @Test
    public void T2_succeeds2() {
        Assert.assertEquals(2, 2);
    }

    @Test
    public void T3_fails() {
        Assert.assertEquals(3, 5);
    }
}

Output:

Starting test! Watchlog:
null

Succeed! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!

Test finished! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!

------------------------------------

Starting test! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!

Succeed! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!
T2_succeeds2(junitexamples.junitrules.TestWatcherRuleTest) success!

Test finished! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!
T2_succeeds2(junitexamples.junitrules.TestWatcherRuleTest) success!

------------------------------------

Starting test! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!
T2_succeeds2(junitexamples.junitrules.TestWatcherRuleTest) success!

Failed! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!
T2_succeeds2(junitexamples.junitrules.TestWatcherRuleTest) success!
T3_fails(junitexamples.junitrules.TestWatcherRuleTest) AssertionError

Test finished! Watchlog:
null
T1_succeeds(junitexamples.junitrules.TestWatcherRuleTest) success!
T2_succeeds2(junitexamples.junitrules.TestWatcherRuleTest) success!
T3_fails(junitexamples.junitrules.TestWatcherRuleTest) AssertionError

------------------------------------

java.lang.AssertionError: 
Expected :3
Actual   :5

TestName Rule

 The TestName Rule allows you to get the current test name inside the test method.

import static org.junit.Assert.assertEquals;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

public class TestNameRule {
    @Rule
    public TestName name = new TestName();

    @Test
    public void testOne() {
        assertEquals("testOne", name.getMethodName());
    }

    @Test
    public void testTwo() {
        assertEquals("testTwo", name.getMethodName());
    }
}

ExternalResources & Class Rule

ExternalResource is a base class for Rules (like TemporaryFolder) that establishes an external resource before a test (a file, socket, server, database connection, etc.), and guarantees to tear it down afterward. This rule is more useful for integration tests.

With ClassRule annotation we can extend ExternalResource operation to multiple classes. It is very useful when we need to repeat test setup/teardown for multiple classes. For example, if we are doing an integration test and we have to start the server before the test and stop it after the test, we should use ClassRule annotation. ClassRule must annotate the public static field.

MyServer.java

import org.junit.rules.ExternalResource;

public class MyServer extends ExternalResource {
    @Override
    protected void before() throws Throwable {
        // start the server
    }

    @Override
    protected void after() {
        // stop the server
    }
}

ServerTest.java

import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({TestFirstServer.class, TestSecondServer.class})
public class ServerTest {
    @ClassRule
    public static MyServer server = new MyServer();

    @Test
    public void testBlah() throws Exception {
        // test something that depends on the server.
    }
}

TestFirstServer.java

import org.junit.Test;

public class TestFirstServer {

    @Test
    public void testBlah() throws Exception {
        System.out.print("Test Server - 1\n");
    }
}

TestSecondServer.java

import org.junit.Test;

public class TestSecondServer {
    @Test
    public void testBlah() throws Exception {
        System.out.print("Test Server - 2\n");
    }
}

RuleChain Rule

With JUnit 4.10, we can order several rules according to our needs using RuleChain rule.

LoggingRule.java

package rulechain;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

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);
                }
            }
        };
    }
}

RuleChainTest.java

package rulechain;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;

public class RuleChainTest {

    @Rule
    public TestRule chain = RuleChain
        .outerRule(new LoggingRule("outer rule"))
        .around(new LoggingRule("middle rule"))
        .around(new LoggingRule("inner rule"));

    @Test
    public void test() {
    }
}

Output:

Starting: outer rule
Starting: middle rule
Starting: inner rule
finished: inner rule
finished: middle rule
finished: outer rule

Custom Rules

In order to write your custom rule, we need to implement the TestRule interface. This interface’s only method that called apply(Statement, Description) returns an instance of Statement. The statement represents our tests within the Junit runtime and Statement#Evaluate() executes them. The description describes the individual test.

“The power from implementing TestRule comes from using a combination of custom constructors, adding methods to the class for use in tests, and wrapping the provided Statement in a new Statement.” (https://github.com/junit-team/junit/wiki/Rules)

Custom Rules can be written simply as follows:

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

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-the-test");
                try {
                    base.evaluate();
                }
                finally {
                    System.out.println("After-the-test");
                }
            }
        };
    }
}

We can use them in our tests by using @Rule annotation

import org.junit.Rule;
import org.junit.Test;

public class MyTest {
    @Rule
    public MyTestRule myTestRule = new MyTestRule();

    @Test
    public void testSanity() throws Exception {
        System.out.println("Test is running…");
    }
}

Output:

Before-the-test
Test is running…
After-the-test

I also want to give a real-life example. The following example is taking a screenshot when our web automation test fails. In the below example, webdriver cannot find a specified id and throws an exception, then it will be caught in evaluate() function’s catch block and it takes a screenshot.

ScreenShotRule.java

import org.apache.commons.io.FileUtils;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;

import java.io.File;
import java.io.IOException;

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; // Report failure to JUnit
                }
            }
            private void takeScreenshot() throws IOException {
                File imageFile = ((TakesScreenshot) ScreenshotRuleTest.driver).getScreenshotAs(OutputType.FILE);
                String failureImageFileName = "SWTestAcademy_Failed_Test.png";
                File failureImageFile = new File(failureImageFileName);
                FileUtils.moveFile(imageFile, failureImageFile);
            }
        };
    }
}

ScreenshotRuleTest.java

import org.junit.Rule;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class ScreenshotRuleTest {

    static WebDriver driver = new ChromeDriver();

    @Rule
    public ScreenshotRule screenshotRule = new ScreenshotRule();

    @Test
    public void testScreenShot() {
        driver.get("https://www.swtestacademy.com");
        driver.findElement(By.id("There_Is_No_This_Kind_Of_Element."));
    }
}

GitHub Project

https://github.com/swtestacademy/junit/tree/junit-rules

Summary of JUnit Rules

  • You learned how to use and the meanings of JUnit Rules.
  • You practiced with examples of  JUnit Rules.

Thanks for reading. 
Onur Baskirt

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.