How do @DisplayName values compose across the levels of a JUnit 5 nested test tree, and what does @DisplayNameGeneration with DisplayNameGenerator.IndicativeSentences change about the reported names?
answer
- display name ≠ unique ID
- default = tree, no concatenation
- generators: Standard, Simple, ReplaceUnderscores, IndicativeSentences
- generator inherited by @Nested classes
- IndicativeSentences joins ancestry, default separator ", "
basics
~20 sEach container and method carries its own display name, and reports render them as a tree: outer > nested > test. IndicativeSentences instead concatenates the enclosing display names with the test's into one sentence. Display names are cosmetic only — filtering and reruns use unique IDs built from class and method names.
solid answer
~50 sBy default every node keeps its **own** name: `@DisplayName` on the outer class, on each `@Nested` class, and on each `@Test` method, rendered by IDEs and reports as a tree — `ShoppingCart > when one item added > total equals item price`. Nothing is concatenated; the hierarchy does the joining visually. `@DisplayNameGeneration(DisplayNameGenerator.IndicativeSentences.class)` changes that for **methods**: the generated name is the enclosing containers' display names plus the method's, joined into a single sentence (default separator `", "`, tunable with `@IndicativeSentencesGeneration`). That reads well in flat, non-tree reports such as CI XML output. Other built-ins are `Standard`, `Simple`, and `ReplaceUnderscores`; a generator declared on a class is inherited by its `@Nested` classes, and `junit.jupiter.displayname.generator.default` sets it suite-wide. Key caveat: display names never affect the **unique ID**. Test selection, `--tests` filters, reruns and history keying use the class/method identifiers, so renaming a display name is safe while renaming the nested class is not.
code
java · 16 lines@DisplayNameGeneration(DisplayNameGenerator.IndicativeSentences.class)
@IndicativeSentencesGeneration(
separator = " -> ",
generator = DisplayNameGenerator.ReplaceUnderscores.class)
class ShoppingCart {
@Nested
class when_one_item_has_been_added {
@Test
void total_equals_the_item_price() {
}
}
}
// reported as:
// ShoppingCart -> when one item has been added -> total equals the item pricego deeper
Know that @DisplayName sets a readable label on classes and methods and that nesting shows as a tree in the IDE.
Add the generator options, especially ReplaceUnderscores, and that a generator on the outer class is inherited by nested classes.
Explain the display-name vs unique-ID split, why IndicativeSentences exists for flat report consumers, and the consequences of renaming a nested class.
Treat naming as a reporting contract: pick a suite-wide generator via configuration parameters, and keep identifiers stable so CI history and flaky-test tracking survive refactors.
## Two independent naming systems Every node in a JUnit Platform test tree has two names: - a **unique ID**, machine-readable and structural, e.g. `[engine:junit-jupiter]/[class:ShoppingCartTest]/[nested-class:WithOneItem]/[method:totalEqualsPrice()]`; - a **display name**, human-readable, shown by IDEs, HTML reports and CI dashboards. Everything in this answer concerns the second. The first is what tooling uses for selection, filtering, rerun-failed and flaky-test history — which is why renaming a `@DisplayName` is a cosmetic change, while renaming the nested class breaks saved run configurations and historical test identity. ## Default composition: a tree, not a string Without a generator, each node's display name is independent: `@DisplayName` on the class if present, otherwise the simple class name; `@DisplayName` on the method if present, otherwise the method name plus parentheses. JUnit does **not** build a combined string. Renderers walk the hierarchy and indent, so a three-level nesting reads as: ``` ShoppingCart when one item has been added total equals the item price ``` This is why nested tests read like a specification: the sentence is assembled by the reader from the path, and each level only has to describe its own delta. ## Display name generators `@DisplayNameGeneration(SomeGenerator.class)` replaces the default naming for a class and, importantly, **is inherited by its `@Nested` classes** — declare it once on the outer class and the whole tree obeys. Built-in generators: - **`Standard`** — the default: simple class name, `method()`. - **`Simple`** — like Standard but drops the empty parentheses from no-arg methods. - **`ReplaceUnderscores`** — turns `total_equals_the_item_price` into `total equals the item price`. This is the most common choice and pairs naturally with underscore-style method names. - **`IndicativeSentences`** — the composing one, below. You can also implement `DisplayNameGenerator` yourself (e.g. strip a `should` prefix, or expand camelCase). And `junit.jupiter.displayname.generator.default` as a configuration parameter sets the default generator for the whole suite without annotating anything. ## What IndicativeSentences does `IndicativeSentences` generates a **method** display name by concatenating the display names of all enclosing containers with the method's own name, separated by `", "` by default. With a class `ShoppingCart`, a nested `when one item has been added`, and a method `total_equals_the_item_price`, the reported name becomes: ``` ShoppingCart, when one item has been added, total equals the item price ``` `@IndicativeSentencesGeneration(separator = " -> ", generator = DisplayNameGenerator.ReplaceUnderscores.class)` customises both the joiner and the generator used for the individual fragments before joining. The motivation is flat consumers. A JUnit XML report, a Slack failure notification or a flaky-test dashboard usually shows only the method's display name, stripped of the tree — and `total equals the item price` alone is useless without knowing which scenario it belonged to. Indicative sentences carry the context into that single string. The cost is redundancy in tree renderers: the IDE already shows the path, so you now read the ancestry twice, and deeply nested trees produce very long lines. Choose per project based on where your team actually reads failures. ## Practical guidance - Name the **outer class** after the subject, each **`@Nested`** after a state or condition ("when …", "given …"), and each **method** after the expected outcome. The concatenation then reads as English in either rendering mode. - Keep display names free of characters that break report tooling; some CI XML consumers dislike newlines or unbalanced quotes. - Do not encode ticket numbers or ownership in display names expecting to filter on them — filtering works on tags (`@Tag`) and unique IDs, not display text. - Parameterized-style names and dynamic names use their own mechanisms; here we are talking strictly about container and method naming in a nested tree. ## Interview framing A strong answer separates the two naming systems, states that default composition is hierarchical rather than concatenated, explains that `IndicativeSentences` flattens the ancestry into the method name for flat consumers, and mentions generator inheritance by `@Nested` classes plus the suite-wide configuration parameter.
- If you rename a @Nested class after the team has been running the suite for months, what breaks?The unique IDs of every test inside it change, because the ID embeds the nested class name. Saved IDE run configurations, --tests style filters and any rerun-failed list that stored IDs stop matching, and CI flaky-test history keyed on the identifier restarts from zero for those tests. Renaming only the @DisplayName has none of those effects.
- Where would you set a display-name generator so it applies to the whole suite without touching every class?Set the junit.jupiter.displayname.generator.default configuration parameter to the generator's fully qualified class name, typically in junit-platform.properties on the test classpath. Annotating a class still overrides it locally, and @Nested classes inherit whatever their enclosing class resolved to.
saying these in an interview costs you the question
- Believing test filtering or rerun works on display names
- Thinking JUnit concatenates enclosing display names by default (it does not — that is IndicativeSentences)
- Assuming a @DisplayNameGeneration on the outer class does not reach @Nested classes
- Claiming @DisplayName can be applied only to classes, or only to methods
- Using display names to encode tags/owners instead of @Tag