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?
answer
- TestRule: (Statement, Description) — metadata only
- MethodRule: (Statement, FrameworkMethod, Object target) — has the instance
- Only TestRule can be a @ClassRule
- MethodRule = older, documented as replaced
- Extend ExternalResource / TestWatcher, not the raw interface
basics
~20 sTestRule.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.
solid answer
~50 s`TestRule` is `Statement apply(Statement base, Description description)`. `Description` is immutable metadata — display name, test class, method name, annotations — and nothing else. Because it needs no instance, a `TestRule` works as either `@Rule` or `@ClassRule`. `MethodRule` is `Statement apply(Statement base, FrameworkMethod method, Object target)`. `FrameworkMethod` is JUnit's reflective wrapper for the test method, and `target` is the **test instance**, so the rule can read or write the object's fields. It is the older interface (both arrived in 4.7) and JUnit's own docs say it has been replaced by `TestRule`, which additionally supports class rules. So: implement `TestRule` by default — usually by extending `ExternalResource` or `TestWatcher` rather than the raw interface. Reach for `MethodRule` only when you genuinely need the instance, for example a rule that populates annotated fields on the test object. `@Rule` accepts both; `@ClassRule` accepts only `TestRule`.
code
java · 20 lines// TestRule: metadata is enough
public class TempServer extends ExternalResource {
private Server server;
@Override protected void before() { server = Server.start(); }
@Override protected void after() { server.stop(); }
}
// MethodRule: needs the live test instance
public class InjectClock implements MethodRule {
@Override
public Statement apply(final Statement base, FrameworkMethod method, final Object target) {
return new Statement() {
@Override public void evaluate() throws Throwable {
Fields.set(target, "clock", Clock.fixed(EPOCH, UTC));
base.evaluate();
}
};
}
}go deeper
Recall that two interfaces exist and that TestRule is the one to implement; naming its apply(Statement, Description) signature is enough.
Give both signatures, explain that MethodRule exists to hand you the test instance, and note that only TestRule supports @ClassRule.
Add the build-time-versus-run-time distinction inside apply, the base classes worth extending, and when the instance genuinely justifies MethodRule.
Discuss the API design lesson — why binding an extension point to the instance limited its scope — and what that implies for how you design your own test infrastructure hooks.
## Two interfaces, one idea Both interfaces do the same thing: take the `Statement` JUnit was going to run and return a `Statement` to run instead. They differ only in what context they are handed. ### TestRule ```java public interface TestRule { Statement apply(Statement base, Description description); } ``` `Description` is a read-only node from the test tree: a display name, the test class, the method name when it describes a method, the annotations on it, and children when it describes a class or suite. It is deliberately inert — you can *inspect* the test, not touch it. Because there is no instance in the signature, the same interface serves per-method rules and class rules, which is why `@ClassRule` accepts `TestRule` only. Typical use: read a custom annotation off the `Description` to configure the rule (`description.getAnnotation(RetryCount.class)`), or use `description.getDisplayName()` in a log line. ### MethodRule ```java public interface MethodRule { Statement apply(Statement base, FrameworkMethod method, Object target); } ``` `FrameworkMethod` is JUnit's wrapper over `java.lang.reflect.Method` with helpers for annotations and invocation. `target` is the actual test-class instance for this method. That instance is the whole point: with it a rule can inject values into fields, read configuration off the object, or validate object state after the test. JUnit's own javadoc notes that `MethodRule` "has been replaced by `TestRule`, which has the added benefit of supporting class rules". ## Which to implement Default to `TestRule`. In practice you rarely implement it directly — you extend one of the supplied base classes: - `ExternalResource` — override `before()` and `after()`; the try/finally is written for you. - `TestWatcher` — override `succeeded`, `failed`, `skipped`, `finished` to observe outcomes without changing them. - `Verifier` — override `verify()` to add a post-test assertion. - `RuleChain` — compose several `TestRule`s in a fixed order. Implement `MethodRule` only when the test instance is required. The classic case is a rule that initialises annotated fields on the test object: nothing in the `TestRule` signature exposes the object, so the older interface is the only option. Third-party libraries that populate fields on the test instance are the usual `MethodRule` implementations you will meet. ## What `@Rule` accepts The `@Rule` annotation explicitly accepts a field or method whose type is a subtype of `TestRule` (preferred) or `MethodRule`. The runner keeps both kinds and applies them around the same method statement, so from the test author's side there is no visible difference. `@ClassRule` accepts `TestRule` only. ## Order of application, briefly When a class has several rule fields, ordering across them is not the source order — it depends on reflection unless you pin it with `@Rule(order = …)` (4.13+) or a `RuleChain`. Mixing `TestRule` and `MethodRule` does not change that; both go through the same container. ## A worked mental model Think of `apply` as a factory for the execution plan. Nothing runs during `apply` — it is called while the runner is *building* the statement chain. Anything you do inside `apply` itself (not inside the returned statement's `evaluate()`) happens for every test at build time, before any test executes. Doing setup directly in `apply` instead of inside the returned statement is a real bug people write: the resource then gets created even for tests that are filtered out or skipped, and it is never torn down. ## Interview framing The crisp answer is: *`TestRule` gets metadata and works everywhere; `MethodRule` gets the live test instance and only works per method; implement `TestRule` unless you need the instance.* Adding that JUnit documents `MethodRule` as superseded, and that `ExternalResource`/`TestWatcher` are the practical entry points, shows you have written rules rather than only used them.
- Why can a `MethodRule` never be used as a `@ClassRule`?Its `apply` signature demands a `FrameworkMethod` and the test instance, and at class scope JUnit has neither — the class rule's statement is built before any instance is created. `TestRule` takes only a `Statement` and a `Description`, so it works at both scopes, which is exactly the advantage JUnit's documentation cites when it says `TestRule` replaced `MethodRule`.
- Should setup code go inside `apply` or inside the returned `Statement`?Inside the returned statement's `evaluate()`, before the call to `base.evaluate()`. `apply` runs while the runner is building the execution plan, so work done there happens even for tests that are later filtered out or skipped, and it never gets the matching teardown from your `finally` block. `apply` should only construct and return the wrapper.
saying these in an interview costs you the question
- Claiming `TestRule` gives access to the test instance — it does not; `Description` is metadata only.
- Implementing `TestRule` from scratch when `ExternalResource` or `TestWatcher` already encodes the try/finally.
- Doing setup work directly in `apply` instead of inside the returned statement.
- Saying `MethodRule` is the modern interface; JUnit documents it as replaced by `TestRule`.
- Assuming `@Rule` accepts only one of the two — it accepts both.