skip to content

@Test & Display Names

What makes a method a Jupiter test and how its name appears in reports. A warm-up question that reveals whether you know JUnit 5's relaxed visibility rules versus JUnit 4's public requirement.

on this pageshow

questions

4

What rules must a Java method (and its enclosing class) satisfy for the JUnit 5 Jupiter engine to discover and run it as a test?

level: juniorimportance: must knowfreq 70%

answer

  1. org.junit.jupiter.api.Test — not org.junit.Test
  2. Not private, not static, not abstract, returns void
  3. Params only if a ParameterResolver supplies them
  4. Class: non-abstract, single constructor, need not be public
  5. @Nested + non-static for inner test classes

basics

~20 s

Annotate it with org.junit.jupiter.api.Test. The method must not be private, static or abstract, and must return void. Parameters are allowed only if a ParameterResolver supplies them (TestInfo, TestReporter, @TempDir...). The class must not be abstract and needs a single constructor; neither class nor method has to be public.

solid answer

~50 s

The method needs `@Test` from **`org.junit.jupiter.api`** — importing JUnit 4's `org.junit.Test` by accident is the classic "my test never runs". Then: - **not `private`** — package-private, protected and public all work, and package-private is idiomatic in Jupiter; - **not `static`**, **not `abstract`**; - **returns `void`** — returning a value was deprecated in 5.11 and fails in current 5.x; - **parameters only if resolvable** — Jupiter injects `TestInfo`, `TestReporter`, `RepetitionInfo`, `@TempDir` paths, and anything a registered `ParameterResolver` (e.g. Spring's or Mockito's extension) supplies; an unresolvable parameter is a `ParameterResolutionException`, not a skip. For the class: it must not be `abstract`, must have exactly **one** constructor (which may itself take resolvable parameters), and — unlike JUnit 4 — need not be public. Inner classes must be non-static and annotated `@Nested` to be discovered. Also note Jupiter's `@Test` has **no attributes**: no `expected`, no `timeout`. Those became `assertThrows` and a separate annotation.

code

java · 18 lines
java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;

class InvoiceWriterTest { // package-private class is fine

    @Test
    void writesFile(@TempDir Path dir) { // resolvable parameter
        assertTrue(new InvoiceWriter().write(dir).toFile().exists());
    }

    @Test
    void reportsItsOwnName(TestInfo testInfo) {
        assertTrue(testInfo.getDisplayName().length() > 0);
    }
}

go deeper

for a junior

Recall the checklist: right import, non-private, non-static, void return, non-abstract class — and that public is not required.

for a middle

Add why parameters are permitted (ParameterResolver injection, with TestInfo/TestReporter/@TempDir as built-ins) and the single-constructor rule for the class.

for a senior

Turn it into a diagnostic: distinguish silent non-discovery (wrong import, missing @Nested) from reported configuration errors (private/static/non-void), and mention the per-method instance lifecycle.

for a principal

Explain the design rationale — attribute-free @Test pushing expected exceptions into assertThrows, and injection as the extension seam that lets Spring/Mockito integrate without inheritance.

## The annotation itself A JUnit 5 test method is one annotated with `org.junit.jupiter.api.Test`. The single most common beginner failure is importing `org.junit.Test` — the JUnit 4 annotation — which the Jupiter engine does not recognise at all. The symptom is not an error; it is a method that is silently never executed. If a test "does nothing", check the import first. Jupiter's `@Test` is also deliberately **attribute-free**. JUnit 4's `@Test(expected = ..., timeout = ...)` has no counterpart: expected exceptions are asserted with `assertThrows(...)` inside the body, and time limits live in a separate annotation. The rationale is that both concerns are assertions about behaviour, not metadata about discovery, and `expected` was too coarse — it passed if the exception came from anywhere in the method, including the setup lines. ## Method-level rules A discovered method must satisfy all of the following: - **Must not be `private`.** Jupiter accepts `public`, `protected` and package-private (default) visibility. Package-private is the idiomatic style in Jupiter, because tests are not an API for anyone outside the package and dropping `public` reduces noise. A `private` `@Test` method is a configuration error, not a silent skip — the engine reports it. - **Must not be `static`.** Test methods run against an instance. `static` is reserved for `@BeforeAll` / `@AfterAll` (unless the class uses `@TestInstance(PER_CLASS)`, which lifts that requirement for those callbacks). - **Must not be `abstract`.** An abstract method has no body to run. Abstract *base classes* declaring concrete `@Test` methods are fine and are a legitimate pattern — the tests are inherited by the concrete subclasses and run there. - **Must return `void`.** Historically a non-void return was ignored; JUnit 5.11 deprecated it with a warning, and current 5.x releases fail the test outright with a clear message. This bites Kotlin and fluent-assertion users who write expression bodies such as `fun test() = assertThat(x).isEqualTo(y)`, which return a value; the fix is an explicit block body returning `Unit`/`void`. - **Parameters are allowed, but only resolvable ones.** Unlike JUnit 4, Jupiter test methods may declare parameters. Every parameter must be supplied by a registered `ParameterResolver`. Built in, you get `TestInfo` (display name, tags, the test method/class), `TestReporter` (publish key/value entries into the report), `RepetitionInfo` (for repeated tests) and `@TempDir` for `Path`/`File`. Extensions add more — Spring's test extension injects beans, Mockito's injects `@Mock` parameters. An unresolvable parameter produces a `ParameterResolutionException` at execution time. ## Class-level rules - **The class must not be abstract**, or there is nothing to instantiate. - **It must have exactly one constructor.** Multiple constructors are ambiguous to the engine. The single constructor may itself declare resolvable parameters — a common way to receive an injected dependency once for the class. - **It need not be public.** JUnit 4 required public test classes; Jupiter does not, because it uses reflection with the necessary access. Package-private test classes are normal. - **Inner classes need `@Nested`** and must be **non-static** inner classes. A plain static nested class without `@Nested` is not scanned as a test class by default discovery. - Discovery itself also depends on the class being on the test classpath and matching the discovery filters, but within a normal test source set, the rules above are what govern whether your method runs. ## Lifecycle context By default (`Lifecycle.PER_METHOD`) the engine constructs a **new instance of the test class for every test method**, which is why instance fields are safe to mutate in a test: the next test gets a fresh object. `@TestInstance(Lifecycle.PER_CLASS)` switches to one instance for the whole class, which lets `@BeforeAll` be non-static but makes leftover field state a real hazard. ## Quick diagnostic checklist for "my test doesn't run" 1. Wrong `@Test` import (`org.junit` instead of `org.junit.jupiter.api`). 2. Method is `private`. 3. Method is `static`. 4. Class is abstract, or has more than one constructor. 5. Nested class missing `@Nested`, or declared `static`. 6. Method returns a value instead of `void`. 7. An unresolvable parameter on the method or constructor. Each of these is either an outright error the engine reports or, in the import case, a silently invisible method — and knowing which is which is the difference between a two-minute fix and an afternoon.

  • Why is a JUnit 5 test method allowed to declare parameters at all, when JUnit 4 forbade it?
    Jupiter builds injection into its extension model: every parameter is supplied by a registered ParameterResolver. That gives the framework a uniform way to hand a test the things it needs — TestInfo, TestReporter, a @TempDir path, a Spring bean, a Mockito mock — without static holders or setup boilerplate. The cost is that an unresolvable parameter is a runtime ParameterResolutionException rather than a compile-time problem.
  • A colleague's @Test method compiles but never appears in the report. What do you check first?
    The import. org.junit.Test is JUnit 4's annotation and the Jupiter engine ignores it entirely, so the method is simply never discovered — no error, no skip, nothing in the report. After that, check whether the method is private or static, whether the class is abstract or has more than one constructor, and whether an inner test class is missing @Nested or is declared static.

saying these in an interview costs you the question

  • Insisting test classes and methods must be public (that was JUnit 4).
  • Using @Test(expected = SomeException.class) — Jupiter's @Test has no attributes; use assertThrows.
  • Making a test method static.
  • Assuming a non-void return value is harmlessly ignored.
  • Thinking a method with parameters cannot be a Jupiter test.

context

open as a page

How do you control the human-readable name a JUnit 5 test shows in IDE output and reports, and when is that better than encoding the description in the method name?

level: juniorimportance: should knowfreq 52%

basics

~20 s

Put @DisplayName("...") on the test class or method. It accepts arbitrary text — spaces, punctuation, non-ASCII — and replaces the method name in IDE and report output. It only affects display; the method name is still the identity used for selection and unique IDs.

open as a page

JUnit 5 can derive readable test labels from method names automatically instead of requiring an annotation on every method. How does that mechanism work, and which strategies ship with the framework?

level: middleimportance: should knowfreq 33%

basics

~20 s

Annotate the class with @DisplayNameGeneration(SomeGenerator.class). Built-ins are Standard (method name plus parentheses), Simple (drops empty parentheses), ReplaceUnderscores (underscores become spaces) and IndicativeSentences (prefixes enclosing class names). You can implement DisplayNameGenerator yourself, or set a suite-wide default via a configuration parameter.

open as a page

A Java codebase has several thousand JUnit 5 tests whose names appear as terse, unreadable identifiers in CI reports. How would you get consistent, human-readable names across the suite without editing every test method, and what would you watch out for?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Set junit.jupiter.displayname.generator.default in a junit-platform.properties file on the test classpath to a generator (ReplaceUnderscores, or a custom camelCase splitter). Existing @DisplayName annotations still win, so nothing is clobbered. Watch out: labels are not identities, so tooling keyed on displayed names will churn.

open as a page