skip to content

JUnit 4 Rules

@Rule and @ClassRule as JUnit 4's composable alternative to runners. Interviewers ask you to map the common rules to their Jupiter replacements.

on this pageshow

questions

6

In JUnit 4, what is a `@Rule`, and what can it do that plain `@Before` and `@After` methods cannot?

level: juniorimportance: must knowfreq 52%

answer

  1. Statement.evaluate() — rule wraps the call
  2. public, non-static field, @Rule
  3. apply(base, Description) returns new Statement
  4. before + after + around (catch/retry/skip/timeout)
  5. composition, not a base class

basics

~20 s

A @Rule is a public, non-static field holding an object that wraps each test method. Because it wraps the call, it can run code before and after the test and also catch failures, retry, time out or skip it. @Before/@After only run beside the test.

solid answer

~50 s

JUnit 4 executes a test method as a `Statement` — an object with `evaluate()`. A rule implements `TestRule.apply(Statement base, Description description)` and returns a **new** statement that typically does setup, calls `base.evaluate()` inside a `try`, and tears down in `finally`. You declare it as a **public, non-static field** annotated `@Rule` (a public non-static method returning the rule also works); the runner validates this and fails the class otherwise. The statement handed to the rule already contains the `@Before` methods, the test method and the `@After` methods, so rule code sits outside all of them. The payoff is control plus reuse. Because the rule owns the call to `base.evaluate()`, it can swallow or translate the exception, retry, apply a timeout on another thread, or throw `AssumptionViolatedException` to skip the test. And it is a *class*, so it composes into any test class — no shared base class, and you can stack several rules.

code

java · 12 lines
java
public class ReportWriterTest {

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    @Test
    public void writesReportFile() throws IOException {
        File out = folder.newFile("report.csv");
        new ReportWriter().writeTo(out);
        assertTrue(out.length() > 0);
    }
}

go deeper

for a junior

Know the declaration (public non-static field, @Rule) and name a built-in such as TemporaryFolder, plus the one-line difference from @Before/@After.

for a middle

Explain the Statement/apply mechanism, that the wrapped statement includes @Before/@After, and give a concrete capability like retry or timeout that methods cannot provide.

for a senior

Discuss when to build a rule versus a base class, ordering between multiple rules, stack-trace noise, and the risk of a rule that hides failures.

for a principal

Frame rules as the JUnit 4 extension point and the composition-over-inheritance answer for shared test infrastructure, and weigh the cost of implicit setup on test readability.

## The statement model JUnit 4 does not invoke your test method straight from the runner. It builds a `Statement` — an abstract class with one method, `void evaluate() throws Throwable` — which, when invoked, runs the whole method-level lifecycle. A rule is an object that is handed that statement and returns a statement to run *in its place*. The modern interface is `org.junit.rules.TestRule`: ```java Statement apply(Statement base, Description description); ``` `base` is what JUnit would otherwise have run; `Description` is read-only metadata about the test (class name, method name, annotations). The idiomatic implementation returns an anonymous `Statement` that does setup, calls `base.evaluate()` in a `try`, and cleans up in `finally`. Rules are therefore *decorators* around the test. ## How you declare one The field must be **public**, **non-static**, and of a type implementing `TestRule` (preferred) or the older `MethodRule`. Annotate it `@Rule`. A public, non-static *method* that returns a rule may also carry `@Rule`. The runner collects these reflectively and validates them: a private field or a static field annotated `@Rule` is an initialization error, so the class fails before a single test runs. Crucially, the statement JUnit passes to a `@Rule` already contains the class's `@Before` methods, the test method and the `@After` methods. Rule code therefore runs strictly outside all of them — a rule's setup happens before every `@Before`, and its cleanup after every `@After`. ## What rules can do that @Before/@After cannot 1. **See and change the outcome.** `@After` runs after the test but cannot observe the exception. A rule catches whatever `base.evaluate()` throws, so it can log the failure, translate it into a better message, add extra failures, or swallow it and let the test pass. 2. **Retry.** Call `base.evaluate()` in a loop until it succeeds or a retry budget is exhausted. 3. **Skip.** Throw `AssumptionViolatedException` before calling `base` and the test is reported as skipped, not failed — a decision `@Before` can make only by throwing the same exception, but a rule can make it for every class that uses it. 4. **Bound execution time.** The built-in `Timeout` rule runs the base statement on another thread and fails the test if it overruns. Nothing method-shaped can do that. 5. **Guarantee cleanup by construction.** The `finally` lives inside the rule, so no test class can forget it. 6. **Compose instead of inherit.** A rule is a reusable class. `@Before` logic is shared by putting it in a base class, and Java gives you exactly one superclass; a test class can hold as many rule fields as it likes. ## Two flavours of instance and class scope `@Rule` fields are per-instance, and JUnit creates a fresh test instance for every test method, so each test gets a fresh rule object and rules re-run per method. `@ClassRule` is the static sibling: a public static `TestRule` field whose statement wraps `@BeforeClass`, the entire class body, and `@AfterClass`, running once. ## The rules that ship with JUnit `TemporaryFolder` (fresh directory per test, deleted afterwards), `ExternalResource` (base class with `before()`/`after()` for any resource), `Timeout`, `TestName` (current method name), `ErrorCollector` (accumulate several failures), `TestWatcher` (callbacks for succeeded/failed/skipped/finished), `Verifier`, `Stopwatch`, `DisableOnDebug`. Most custom rules extend `ExternalResource` or `TestWatcher` rather than implementing `TestRule` from scratch. ## Pitfalls The rule field is initialised per test instance, so do not put expensive state in a `@Rule` if it can be a `@ClassRule`. Rules add frames to stack traces, which can obscure the real failure point. Order between multiple rule fields is not the source order — it depends on reflection unless you pin it with `@Rule(order = …)` (JUnit 4.13) or a `RuleChain`. And a rule that quietly swallows exceptions is a debugging trap: if it catches, it should re-throw or report. ## Why interviewers ask Rules are the JUnit 4 extension point. Understanding that a rule *wraps* rather than *precedes* the test is what separates a candidate who copy-pastes `TemporaryFolder` from one who can build shared test infrastructure.

  • Why must a `@Rule` field be public and non-static?
    The runner reads rule fields reflectively through its `TestClass` model and validates them before running anything, and it requires public access rather than forcing accessibility. Non-static is required because a `@Rule` is per-test-instance: JUnit creates a new instance for every test method, so each test gets its own rule object. A static field annotated `@Rule` is an initialization error — that is `@ClassRule`'s job.
  • Can a rule tell whether the test passed or failed?
    Yes. Since the rule calls `base.evaluate()` itself, it sees the `Throwable` if one is thrown and can act on it. The built-in `TestWatcher` packages exactly this: override `succeeded`, `failed`, `skipped` or `finished` and it invokes the right callback based on what came out of the wrapped statement — the usual way to dump diagnostics on failure.

@Before/@After are the doorman who greets you and tidies up after you leave; a rule is the whole building around the room — it can lock the door, restart the visit, or throw you out early.

saying these in an interview costs you the question

  • Saying a `@Rule` runs once per class — that is `@ClassRule`; a `@Rule` runs around every test method.
  • Declaring the rule field private (or static) and expecting it to work — the runner rejects the class with a validation error.
  • Claiming a rule cannot influence the result; a rule can swallow, translate or add failures because it owns the call to `base.evaluate()`.
  • Thinking `@RunWith` is required for rules to work — the default runner already applies them.
  • Assuming rule code runs between `@Before` and the test; it runs outside `@Before` and `@After` entirely.

context

open as a page

In JUnit 4, what is the difference between a field annotated `@Rule` and one annotated `@ClassRule` — how must each be declared, and what does each one's wrapped statement cover?

level: middleimportance: must knowfreq 44%

basics

~20 s

@Rule is a public non-static field; its statement covers the @Before methods, one test method and the @After methods, so it runs per test. @ClassRule is a public static TestRule field; its statement covers @BeforeClass, the whole class body and @AfterClass, so it runs once.

open as a page

JUnit 4 ships several ready-made rules — `TemporaryFolder`, `ExternalResource`, `Timeout`, `ExpectedException`, `TestName` and `ErrorCollector`. What does each one do, and which of them is now discouraged?

level: juniorimportance: should knowfreq 38%

basics

~20 s

TemporaryFolder makes a fresh directory per test and deletes it after. ExternalResource is a base class with before()/after() for any resource. Timeout fails an overlong test. TestName exposes the current method name. ErrorCollector gathers multiple failures. ExpectedException is discouraged — use Assert.assertThrows.

open as a page

JUnit 4 defines two rule interfaces, `org.junit.rules.TestRule` and `org.junit.rules.MethodRule`. What does each one's `apply` method receive, and which should new code implement?

level: middleimportance: should knowfreq 30%

basics

~20 s

TestRule.apply takes (Statement base, Description description) — metadata only — and works for both @Rule and @ClassRule. MethodRule.apply takes (Statement base, FrameworkMethod method, Object target), giving you the test instance. TestRule is the recommended interface; use MethodRule only when you must touch the instance.

open as a page

A JUnit 4 test class declares three `@Rule` fields and one of them must set up before the others. What order does JUnit apply multiple rules in by default, and how do you make that order deterministic?

level: seniorimportance: should knowfreq 28%

basics

~20 s

By default the order across several rule fields depends on the JVM's reflection order and is undefined. Pin it with @Rule(order = N) (JUnit 4.13+), where a lower value is the outer rule that starts first, or by combining rules into one RuleChain field.

open as a page

Across a large JUnit 4 suite, dozens of test classes need the same expensive setup — a database container, a seeded workspace, a fixed clock. How do you weigh implementing that as a shared rule, an abstract base test class, or a static singleton, and what are the failure modes of each?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Rules compose (a class can hold many), a base class does not (Java has one superclass) and hides setup in an invisible parent. Singletons give suite-wide reuse but no teardown or isolation. The usual answer is a rule that fronts a lazily started singleton, with a per-test rule restoring isolation.

open as a page