skip to content

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%

answer

  1. TemporaryFolder — fresh dir, auto-deleted, builder().assureDeletion()
  2. ExternalResource — before()/after() base class
  3. Timeout — separate thread, withLookingForStuckThread
  4. TestWatcher/Stopwatch — observe, don't change
  5. ExpectedException.none() deprecated 4.13 → assertThrows

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.

solid answer

~50 s

- **`TemporaryFolder`** — creates a directory before each test and deletes it recursively afterwards; `newFile()`/`newFolder()` create children inside it. Since 4.13 a builder with `assureDeletion()` makes the test fail if cleanup does not succeed. - **`ExternalResource`** — the base class you extend for any resource with a lifecycle: override `before()` and `after()`; it writes the try/finally for you. Works as `@Rule` or `@ClassRule`. - **`Timeout`** — fails a test that exceeds a limit (`Timeout.seconds(5)`), running the test on a separate thread; the builder adds `withLookingForStuckThread(true)` to name the culprit thread. - **`TestName`** — `name.getMethodName()` gives the current test's name, handy in log or file names. - **`ErrorCollector`** — collect several failures in one run instead of stopping at the first. - **`ExpectedException`** — the old declarative way to assert an exception; `ExpectedException.none()` is **deprecated since 4.13** in favour of `Assert.assertThrows`, which scopes the expectation to one statement and returns the exception for further assertions. Also worth knowing: `TestWatcher`, `Verifier`, `Stopwatch` and `DisableOnDebug`.

code

java · 16 lines
java
public class ExportTest {

    @Rule public TemporaryFolder folder = new TemporaryFolder();
    @Rule public TestName name = new TestName();
    @Rule public TestRule timeout = new DisableOnDebug(Timeout.seconds(5));
    @Rule public ErrorCollector collector = new ErrorCollector();

    @Test
    public void exportsAllColumns() throws Exception {
        File out = folder.newFile(name.getMethodName() + ".csv");
        Exporter.export(rows(), out);

        collector.checkThat(header(out), is("id,name,total"));
        collector.checkThat(lineCount(out), is(3));
    }
}

go deeper

for a junior

Name each rule and its one-line job, and know that exception testing today uses assertThrows.

for a middle

Add the mechanics: ExternalResource as the base class you extend, Timeout running on a separate thread, and why ExpectedException was deprecated.

for a senior

Talk about operational touches — DisableOnDebug around timeouts, assureDeletion, stuck-thread reporting, and using TestWatcher to capture diagnostics on failure.

for a principal

Position the catalogue as a design vocabulary: resource rules, observer rules and assertion rules, and set team conventions for which to use where.

## Why built-in rules matter Almost every custom rule people write is a variation on one of the shipped ones. Knowing the catalogue keeps you from reinventing them, and each illustrates a different capability of the rule mechanism. ## TemporaryFolder Creates a fresh directory before the test and deletes it (recursively) afterwards, so file-touching tests never collide or leave litter. Use `folder.newFile("x.txt")`, `folder.newFolder("sub")`, `folder.getRoot()`. Since JUnit 4.13, `TemporaryFolder.builder().assureDeletion().build()` makes the rule *fail* the test when deletion does not succeed — useful on platforms where an open handle silently blocks cleanup. As a `@Rule` you get one folder per test; as a `@ClassRule`, one per class. ## ExternalResource An abstract `TestRule` with two hooks: ```java protected void before() throws Throwable {} protected void after() {} ``` It is the correct starting point for "start something, then stop it" — a server, a connection pool, a container, a temp workspace. It already handles calling `after()` in a `finally`, so cleanup runs even when the test throws. `TemporaryFolder` itself extends it. ## Timeout Fails a test that runs longer than a limit. `@Rule public Timeout timeout = Timeout.seconds(5);` applies the limit to *every* method in the class, which is its advantage over `@Test(timeout = …)` on each method. It executes the statement on a separate thread and interrupts it on expiry — so a test that ignores interruption may leave the thread running. `Timeout.builder().withTimeout(5, SECONDS).withLookingForStuckThread(true).build()` adds the stuck thread's stack trace to the failure. Note that JUnit documents `Timeout` as undefined behaviour when used as a `@ClassRule`. ## DisableOnDebug Wraps another rule and disables it when the JVM is running under a debugger: `@Rule public TestRule timeout = new DisableOnDebug(Timeout.seconds(5));`. Without it, every debugging session ends in a spurious timeout failure. This is the classic "nice-to-know" answer that shows real usage. ## TestName `@Rule public TestName name = new TestName();` then `name.getMethodName()` inside the test. Useful for naming generated artifacts or log lines per test. It is a thin `TestWatcher` that records the description. ## ErrorCollector Lets a test report several problems in one run rather than stopping at the first assertion failure: `collector.checkThat(actual, is(expected))`, `collector.addError(throwable)`, `collector.checkSucceeds(callable)`. Failures accumulate and are reported together at the end. It extends `Verifier`, the general "run an extra check after the test body" rule. Both are documented as undefined behaviour at class scope. ## TestWatcher Not a resource but an observer: override `succeeded(Description)`, `failed(Throwable, Description)`, `skipped(AssumptionViolatedException, Description)`, `starting`, `finished`. It never changes the outcome — it is how you dump a screenshot, page source or server log on failure. `Stopwatch` is a sibling that times each test. ## ExpectedException — the discouraged one The old declarative style: ```java @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void rejectsBadInput() { thrown.expect(IllegalArgumentException.class); thrown.expectMessage("amount"); service.charge(-1); } ``` `ExpectedException.none()` is deprecated as of JUnit 4.13, with the javadoc pointing at `Assert.assertThrows`. The reasons are practical: the expectation is declared before the action, so any statement after it — including setup lines — could satisfy it, hiding the real source of the exception; and once the rule catches the exception you cannot make further assertions on it. `assertThrows` scopes the expectation to exactly one lambda and hands the exception back: ```java IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> service.charge(-1)); assertThat(e.getMessage(), containsString("amount")); ``` ## Picking the right one Resource with a lifecycle → `ExternalResource`. Files → `TemporaryFolder`. Diagnostics on failure → `TestWatcher`. Several independent checks in one test → `ErrorCollector` (but consider whether the test is doing too much). Exceptions → `assertThrows`, not `ExpectedException`. Hanging tests → `Timeout`, wrapped in `DisableOnDebug` so debugging still works.

  • Why is `Assert.assertThrows` preferred over the `ExpectedException` rule?
    `ExpectedException` declares the expectation before the action, so an exception thrown by *any* later line — including setup — satisfies it, which can hide the true failure and even make a broken test pass. `assertThrows` brackets exactly one call, and it returns the caught exception so you can assert on its message, cause or fields afterwards. JUnit deprecated `ExpectedException.none()` in 4.13 for these reasons.
  • What is `DisableOnDebug` for?
    It wraps another rule — almost always a `Timeout` — and disables it when the JVM was started with debugging enabled. Without it, stepping through a test in a debugger blows the time limit and the run ends in a bogus timeout failure. It is a rule that decorates a rule, which is a neat demonstration that rules compose.

saying these in an interview costs you the question

  • Still teaching `ExpectedException.none()` as current practice — its factory has been deprecated since 4.13.
  • Thinking `TemporaryFolder` needs manual cleanup, or that it silently fails when deletion is blocked (4.13's `assureDeletion()` exists precisely to surface that).
  • Believing `Timeout` runs the test on the same thread — it does not, which is why it can flag a stuck thread.
  • Using `ErrorCollector` to paper over a test that asserts on several unrelated behaviours.
  • Assuming `TestWatcher` can change a failing test into a passing one; it observes outcomes only.

context