JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.
Rules provide a way to add reusable behavior around test execution. They can be applied to individual test methods (@Rule) or entire test classes (@ClassRule). Rules are more flexible and composable than inheritance-based approaches.
The core interface for implementing custom rules. Rules wrap test execution with custom behavior.
/**
* Interface for test rules
* A rule wraps test execution with custom behavior
*/
public interface TestRule {
/**
* Modifies the Statement that executes a test
* @param base - The Statement to be modified
* @param description - Description of the test
* @return Modified Statement
*/
Statement apply(Statement base, Description description);
}Usage Examples:
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class LoggingRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
System.out.println("Starting: " + description.getMethodName());
try {
base.evaluate();
System.out.println("Passed: " + description.getMethodName());
} catch (Throwable t) {
System.out.println("Failed: " + description.getMethodName());
throw t;
}
}
};
}
}
// Usage
public class MyTest {
@Rule
public TestRule loggingRule = new LoggingRule();
@Test
public void testSomething() {
// Logging happens automatically
assertTrue(true);
}
}Creates temporary files and folders that are automatically deleted after the test. Useful for tests that need file system access.
/**
* Rule for creating temporary files and folders
* Automatically deleted after test completion
*/
public class TemporaryFolder extends ExternalResource {
/**
* Creates a TemporaryFolder using default temp directory
*/
public TemporaryFolder();
/**
* Creates a TemporaryFolder in specified parent folder
* @param parentFolder - Parent directory for temp folder
*/
public TemporaryFolder(File parentFolder);
/**
* Get the root temporary folder
* @return Root folder (created lazily)
*/
public File getRoot();
/**
* Create a new temporary file with random name
* @return Created file
* @throws IOException if file cannot be created
*/
public File newFile() throws IOException;
/**
* Create a new temporary file with specified name
* @param filename - Name for the file
* @return Created file
* @throws IOException if file cannot be created
*/
public File newFile(String filename) throws IOException;
/**
* Create a new temporary folder with random name
* @return Created folder
* @throws IOException if folder cannot be created
*/
public File newFolder() throws IOException;
/**
* Create a new temporary folder with specified name
* @param folderName - Name for the folder
* @return Created folder
* @throws IOException if folder cannot be created
*/
public File newFolder(String folderName) throws IOException;
/**
* Create a new temporary folder with path segments
* @param folderNames - Path segments for nested folders
* @return Created folder
* @throws IOException if folder cannot be created
*/
public File newFolder(String... folderNames) throws IOException;
/**
* Delete all files and folders
*/
public void delete();
/**
* Builder for configuring TemporaryFolder
*/
public static class Builder {
public Builder parentFolder(File parentFolder);
public Builder assureDeletion();
public TemporaryFolder build();
}
public static Builder builder();
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.*;
import static org.junit.Assert.*;
public class FileTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testFileCreation() throws IOException {
File file = folder.newFile("test.txt");
assertTrue(file.exists());
// File automatically deleted after test
}
@Test
public void testFileWrite() throws IOException {
File file = folder.newFile("data.txt");
FileWriter writer = new FileWriter(file);
writer.write("Hello, World!");
writer.close();
BufferedReader reader = new BufferedReader(new FileReader(file));
assertEquals("Hello, World!", reader.readLine());
reader.close();
}
@Test
public void testFolderCreation() throws IOException {
File subFolder = folder.newFolder("subfolder");
File nestedFile = new File(subFolder, "nested.txt");
nestedFile.createNewFile();
assertTrue(nestedFile.exists());
}
@Test
public void testNestedFolders() throws IOException {
File nested = folder.newFolder("level1", "level2", "level3");
assertTrue(nested.exists());
assertTrue(nested.isDirectory());
}
}
// Using builder
public class ConfiguredFolderTest {
@Rule
public TemporaryFolder folder = TemporaryFolder.builder()
.parentFolder(new File("/tmp"))
.assureDeletion()
.build();
}DEPRECATED since JUnit 4.13 - Use Assert.assertThrows() instead.
Verifies that code throws expected exceptions with specific properties. More flexible than @Test(expected=...).
/**
* Rule for verifying expected exceptions
* Provides detailed exception matching
*
* @deprecated Since 4.13 - Use Assert.assertThrows(Class, ThrowingRunnable) instead
*/
@Deprecated
public class ExpectedException implements TestRule {
/**
* Create an ExpectedException rule
* @return New ExpectedException instance
* @deprecated Since 4.13 - Use Assert.assertThrows() instead
*/
@Deprecated
public static ExpectedException none();
/**
* Expect exception of specific type
* @param type - Expected exception class
*/
public void expect(Class<? extends Throwable> type);
/**
* Expect exception matching a matcher
* @param matcher - Hamcrest matcher for exception
*/
public void expect(Matcher<?> matcher);
/**
* Expect exception message containing specific text
* @param substring - Expected substring in message
*/
public void expectMessage(String substring);
/**
* Expect exception message matching a matcher
* @param matcher - Hamcrest matcher for message
*/
public void expectMessage(Matcher<String> matcher);
/**
* Expect exception cause matching a matcher
* @param expectedCause - Matcher for cause
*/
public void expectCause(Matcher<? extends Throwable> expectedCause);
/**
* Check if any exception is expected
* @return true if exception expected
*/
public boolean isAnyExceptionExpected();
/**
* Configure to handle AssertionErrors as exceptions
* @return This instance for chaining
*/
public ExpectedException handleAssertionErrors();
/**
* Configure to handle AssumptionViolatedExceptions
* @return This instance for chaining
*/
public ExpectedException handleAssumptionViolatedExceptions();
/**
* Configure custom message for missing exception
* @param message - Custom message
* @return This instance for chaining
*/
public ExpectedException reportMissingExceptionWithMessage(String message);
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.CoreMatchers.*;
public class ExceptionTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testExceptionType() {
thrown.expect(IllegalArgumentException.class);
throw new IllegalArgumentException("Invalid argument");
}
@Test
public void testExceptionMessage() {
thrown.expect(NullPointerException.class);
thrown.expectMessage("Cannot be null");
processNull(null);
}
@Test
public void testExceptionMessagePattern() {
thrown.expect(IllegalStateException.class);
thrown.expectMessage(containsString("invalid state"));
performInvalidOperation();
}
@Test
public void testExceptionCause() {
thrown.expect(RuntimeException.class);
thrown.expectCause(isA(IOException.class));
throw new RuntimeException("Wrapped", new IOException("IO error"));
}
@Test
public void testMultipleConditions() {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage(startsWith("Invalid"));
thrown.expectMessage(containsString("parameter"));
validateParameter(-1);
}
}Applies a timeout to all test methods in a class. Test fails if it runs longer than specified time.
/**
* Rule for enforcing test timeouts
*/
public class Timeout implements TestRule {
/**
* Create timeout in milliseconds
* @param millis - Timeout in milliseconds
* @return Timeout rule
*/
public static Timeout millis(long millis);
/**
* Create timeout in seconds
* @param seconds - Timeout in seconds
* @return Timeout rule
*/
public static Timeout seconds(long seconds);
/**
* Get builder for configuring timeout
* @return Builder instance
*/
public static Builder builder();
/**
* Builder for Timeout configuration
*/
public static class Builder {
/**
* Set timeout duration
* @param timeout - Timeout value
* @param unit - Time unit
* @return This builder
*/
public Builder withTimeout(long timeout, TimeUnit unit);
/**
* Enable looking for stuck threads
* @param enable - Whether to look for stuck threads
* @return This builder
*/
public Builder withLookingForStuckThread(boolean enable);
/**
* Build the Timeout rule
* @return Configured Timeout
*/
public Timeout build();
}
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import java.util.concurrent.TimeUnit;
public class TimeoutTest {
@Rule
public Timeout globalTimeout = Timeout.seconds(10);
@Test
public void testFastOperation() {
// Must complete within 10 seconds
performQuickOperation();
}
@Test
public void testAnotherOperation() {
// Also must complete within 10 seconds
performOperation();
}
}
// Using builder
public class ConfiguredTimeoutTest {
@Rule
public Timeout timeout = Timeout.builder()
.withTimeout(5, TimeUnit.SECONDS)
.withLookingForStuckThread(true)
.build();
@Test
public void testWithStuckThreadDetection() {
performOperation();
}
}
// Milliseconds timeout
public class MillisTimeoutTest {
@Rule
public Timeout timeout = Timeout.millis(500);
@Test
public void testVeryFastOperation() {
// Must complete within 500ms
quickOperation();
}
}Provides access to the name of the currently executing test method. Useful for debugging and logging.
/**
* Rule for accessing current test method name
*/
public class TestName extends TestWatcher {
/**
* Get the name of the current test method
* @return Test method name
*/
public String getMethodName();
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import static org.junit.Assert.*;
public class NamedTest {
@Rule
public TestName testName = new TestName();
@Test
public void testA() {
assertEquals("testA", testName.getMethodName());
System.out.println("Running: " + testName.getMethodName());
}
@Test
public void testB() {
assertEquals("testB", testName.getMethodName());
}
@Test
public void testWithLogging() {
String currentTest = testName.getMethodName();
log("Starting test: " + currentTest);
performOperation();
log("Finished test: " + currentTest);
}
}Collects multiple errors in a single test, allowing the test to continue after failures. Useful for validating multiple conditions.
/**
* Rule for collecting multiple errors
* Test continues after failures and reports all errors at end
*/
public class ErrorCollector extends Verifier {
/**
* Add an error
* @param error - Throwable to collect
*/
public void addError(Throwable error);
/**
* Check a condition and collect error if false
* @param value - Value to check
* @param matcher - Matcher to apply
*/
public <T> void checkThat(T value, Matcher<T> matcher);
/**
* Check a condition with reason
* @param reason - Reason for the check
* @param value - Value to check
* @param matcher - Matcher to apply
*/
public <T> void checkThat(String reason, T value, Matcher<T> matcher);
/**
* Call a callable and collect errors
* @param callable - Code to execute
* @return Result of callable
*/
public <T> T checkSucceeds(Callable<T> callable);
/**
* Check that runnable throws expected exception
* @param expectedThrowable - Expected exception type
* @param runnable - Code to execute
*/
public <T extends Throwable> void checkThrows(
Class<T> expectedThrowable,
ThrowingRunnable runnable
);
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import static org.hamcrest.CoreMatchers.*;
public class MultipleChecksTest {
@Rule
public ErrorCollector collector = new ErrorCollector();
@Test
public void testMultipleValues() {
// All checks are performed even if some fail
collector.checkThat("First check", 1 + 1, is(2));
collector.checkThat("Second check", 2 + 2, is(4));
collector.checkThat("Third check", 3 + 3, is(6));
// If any fail, all failures are reported together
}
@Test
public void testTableData() {
int[][] table = getTestData();
for (int i = 0; i < table.length; i++) {
for (int j = 0; j < table[i].length; j++) {
collector.checkThat(
"Cell [" + i + "][" + j + "]",
table[i][j],
greaterThan(0)
);
}
}
// Reports all failing cells, not just the first one
}
@Test
public void testWithCallable() {
String result = collector.checkSucceeds(() -> {
return performOperation();
});
assertNotNull(result);
}
}Base class for rules that set up and tear down external resources. Template for resource management.
/**
* Base class for rules that manage external resources
*/
public abstract class ExternalResource implements TestRule {
/**
* Override to set up resource before test
*/
protected void before() throws Throwable;
/**
* Override to tear down resource after test
*/
protected void after();
}Usage Examples:
import org.junit.rules.ExternalResource;
import org.junit.Rule;
import org.junit.Test;
public class DatabaseResource extends ExternalResource {
private Database database;
@Override
protected void before() throws Throwable {
database = new Database();
database.connect();
database.initialize();
}
@Override
protected void after() {
if (database != null) {
database.disconnect();
}
}
public Database getDatabase() {
return database;
}
}
public class DatabaseTest {
@Rule
public DatabaseResource dbResource = new DatabaseResource();
@Test
public void testQuery() {
Database db = dbResource.getDatabase();
Result result = db.query("SELECT * FROM users");
assertNotNull(result);
}
}
// Server resource example
public class ServerResource extends ExternalResource {
private TestServer server;
private int port;
public ServerResource(int port) {
this.port = port;
}
@Override
protected void before() throws Throwable {
server = new TestServer(port);
server.start();
}
@Override
protected void after() {
if (server != null) {
server.stop();
}
}
public String getUrl() {
return "http://localhost:" + port;
}
}Chains multiple rules to execute in a specific order. Useful when rules have dependencies.
/**
* Chains multiple rules to execute in order
*/
public class RuleChain implements TestRule {
/**
* Create an empty rule chain
* @return Empty RuleChain
*/
public static RuleChain emptyRuleChain();
/**
* Create a rule chain starting with a rule
* @param outerRule - First rule in chain
* @return RuleChain with the rule
*/
public static RuleChain outerRule(TestRule outerRule);
/**
* Add a rule to the chain
* @param rule - Rule to add
* @return This RuleChain
*/
public RuleChain around(TestRule rule);
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.ExternalResource;
import org.junit.rules.TestRule;
public class ChainedRulesTest {
private DatabaseResource database = new DatabaseResource();
private ServerResource server = new ServerResource(8080);
@Rule
public TestRule chain = RuleChain
.outerRule(database) // First: set up database
.around(server); // Then: start server (can use database)
@Test
public void testWithBothResources() {
// Both database and server are available
makeRequestToServer();
}
}
// More complex chain
public class ComplexChainTest {
@Rule
public TestRule chain = RuleChain
.outerRule(new LoggingRule())
.around(new TimeoutRule())
.around(new ResourceRule());
@Test
public void test() {
// Rules execute in order: Logging -> Timeout -> Resource
// Cleanup happens in reverse: Resource -> Timeout -> Logging
}
}Observes test execution and provides hooks for test lifecycle events. Base class for monitoring rules.
/**
* Base class for rules that observe test execution
*/
public abstract class TestWatcher implements TestRule {
/**
* Called when test succeeds
* @param description - Test description
*/
protected void succeeded(Description description);
/**
* Called when test fails
* @param e - Throwable that caused failure
* @param description - Test description
*/
protected void failed(Throwable e, Description description);
/**
* Called when test is skipped
* @param e - AssumptionViolatedException
* @param description - Test description
*/
protected void skipped(AssumptionViolatedException e, Description description);
/**
* Called when test is about to start
* @param description - Test description
*/
protected void starting(Description description);
/**
* Called when test finishes (success or failure)
* @param description - Test description
*/
protected void finished(Description description);
}Usage Examples:
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.Rule;
import org.junit.Test;
public class DetailedWatcher extends TestWatcher {
@Override
protected void starting(Description description) {
System.out.println("Starting: " + description.getMethodName());
}
@Override
protected void succeeded(Description description) {
System.out.println("PASSED: " + description.getMethodName());
}
@Override
protected void failed(Throwable e, Description description) {
System.out.println("FAILED: " + description.getMethodName());
System.out.println("Error: " + e.getMessage());
}
@Override
protected void finished(Description description) {
System.out.println("Finished: " + description.getMethodName());
}
}
public class WatchedTest {
@Rule
public TestWatcher watcher = new DetailedWatcher();
@Test
public void testSomething() {
assertTrue(true);
}
}
// Screenshot on failure example
public class ScreenshotOnFailure extends TestWatcher {
@Override
protected void failed(Throwable e, Description description) {
String testName = description.getMethodName();
takeScreenshot(testName + "_failure.png");
}
private void takeScreenshot(String filename) {
// Screenshot logic
}
}Measures and logs test execution time. Provides hooks for accessing timing information.
/**
* Rule for measuring test execution time
*/
public class Stopwatch implements TestRule {
/**
* Get runtime in milliseconds
* @param unit - Time unit to convert to
* @return Runtime in specified unit
*/
public long runtime(TimeUnit unit);
/**
* Called when test succeeds
* @param nanos - Execution time in nanoseconds
* @param description - Test description
*/
protected void succeeded(long nanos, Description description);
/**
* Called when test fails
* @param nanos - Execution time in nanoseconds
* @param e - Throwable that caused failure
* @param description - Test description
*/
protected void failed(long nanos, Throwable e, Description description);
/**
* Called when test is skipped
* @param nanos - Time until skip
* @param e - AssumptionViolatedException
* @param description - Test description
*/
protected void skipped(long nanos, AssumptionViolatedException e, Description description);
/**
* Called when test finishes
* @param nanos - Execution time in nanoseconds
* @param description - Test description
*/
protected void finished(long nanos, Description description);
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Stopwatch;
import org.junit.runner.Description;
import java.util.concurrent.TimeUnit;
public class TimingTest {
@Rule
public Stopwatch stopwatch = new Stopwatch() {
@Override
protected void succeeded(long nanos, Description description) {
long millis = TimeUnit.NANOSECONDS.toMillis(nanos);
System.out.println(description.getMethodName() + " succeeded in " + millis + "ms");
}
@Override
protected void failed(long nanos, Throwable e, Description description) {
long millis = TimeUnit.NANOSECONDS.toMillis(nanos);
System.out.println(description.getMethodName() + " failed in " + millis + "ms");
}
@Override
protected void finished(long nanos, Description description) {
long millis = TimeUnit.NANOSECONDS.toMillis(nanos);
System.out.println(description.getMethodName() + " finished in " + millis + "ms");
}
};
@Test
public void performanceTest() {
// Timing is automatically recorded
performOperation();
}
}
// Logging slow tests
public class SlowTestLogger extends Stopwatch {
private static final long THRESHOLD_MS = 1000;
@Override
protected void finished(long nanos, Description description) {
long millis = TimeUnit.NANOSECONDS.toMillis(nanos);
if (millis > THRESHOLD_MS) {
System.out.println("SLOW TEST: " + description.getMethodName()
+ " took " + millis + "ms");
}
}
}Base class for rules that verify state after test execution. Similar to custom assertions.
/**
* Base class for rules that verify conditions after test
*/
public abstract class Verifier implements TestRule {
/**
* Override to verify conditions after test
* Throw exception if verification fails
*/
protected void verify() throws Throwable;
}Usage Examples:
import org.junit.rules.Verifier;
import org.junit.Rule;
import org.junit.Test;
public class LogVerifier extends Verifier {
private Logger logger;
public LogVerifier(Logger logger) {
this.logger = logger;
}
@Override
protected void verify() {
if (logger.hasErrors()) {
throw new AssertionError("Test produced error logs: " + logger.getErrors());
}
}
}
public class VerifiedTest {
private Logger logger = new Logger();
@Rule
public Verifier logVerifier = new LogVerifier(logger);
@Test
public void testSomething() {
// Test code
// After test, verifier checks that no errors were logged
}
}
// State verification example
public class StateVerifier extends Verifier {
private SystemState state;
public StateVerifier(SystemState state) {
this.state = state;
}
@Override
protected void verify() {
if (!state.isClean()) {
throw new AssertionError("System left in dirty state");
}
}
}Wraps another rule and disables it when running in debug mode. Useful for timeout rules that interfere with debugging.
/**
* Disables a rule when running in debug mode
*/
public class DisableOnDebug implements TestRule {
/**
* Create a rule that is disabled during debugging
* @param rule - Rule to wrap
*/
public DisableOnDebug(TestRule rule);
/**
* Check if currently debugging
* @return true if debug mode detected
*/
public boolean isDebugging();
}Usage Examples:
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.Timeout;
public class DebuggableTest {
@Rule
public TestRule timeout = new DisableOnDebug(Timeout.seconds(10));
@Test
public void testWithTimeout() {
// Timeout applies normally, but is disabled when debugging
// so you can use breakpoints without timeout failures
performOperation();
}
}/**
* Represents a statement in test execution
*/
public abstract class Statement {
/**
* Execute the statement
*/
public abstract void evaluate() throws Throwable;
}
/**
* Legacy rule interface (prefer TestRule)
*/
@Deprecated
public interface MethodRule {
Statement apply(Statement base, FrameworkMethod method, Object target);
}Install with Tessl CLI
npx tessl i tessl/maven-junit--junit