skip to content

How do @EnumSource and the @ParameterizedTest name template work, including filtering enum constants and customizing display names?

level: middleimportance: should knowfreq 40%

answer

  1. @EnumSource = one invocation per enum constant
  2. names + mode: INCLUDE/EXCLUDE/MATCH_ALL/MATCH_ANY
  3. name template placeholders: {index} {arguments} {0} {1}
  4. {index} 1-based, {0} 0-based
  5. Named.of(...) labels opaque arguments

basics

~20 s

@EnumSource runs the test once per constant of an enum, injecting each constant. You can include or exclude specific names with its mode/names attributes. The name attribute on @ParameterizedTest sets each invocation's display label using placeholders like {index} and {0}.

solid answer

~50 s

@EnumSource feeds the constants of an enum into a parameterized test, one invocation per constant, injecting the constant into a parameter of that enum type. By default it uses every constant; you narrow the set with `names` plus a `mode`: `INCLUDE` (default) or `EXCLUDE` for explicit lists, and `MATCH_ALL`/`MATCH_ANY` to filter by regex. You can target a specific enum via the `value` attribute or let JUnit infer it from the parameter type. Separately, the `@ParameterizedTest(name = ...)` attribute is a **display-name template** controlling how each invocation appears in reports. Placeholders include `{index}` (1-based invocation number), `{arguments}` (all args), `{argumentsWithNames}`, and positional `{0}`, `{1}` … for individual arguments. A clear template like `name = "[{index}] {0} should be active"` makes failures self-describing. These two concerns are orthogonal: @EnumSource is *what data*, the name template is *how it's labelled*.

code

java · 23 lines
java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.EnumSource.Mode;
import static org.junit.jupiter.api.Assertions.assertTrue;

class StatusTest {

    enum Status { ACTIVE, SUSPENDED, CLOSED }

    // every constant except CLOSED, with a readable display name
    @ParameterizedTest(name = "[{index}] {0} is non-terminal")
    @EnumSource(value = Status.class, names = "CLOSED", mode = Mode.EXCLUDE)
    void nonTerminalStatuses(Status s) {
        assertTrue(s != Status.CLOSED);
    }

    // regex filter: only names ending in ED
    @ParameterizedTest
    @EnumSource(value = Status.class, names = ".*ED$", mode = Mode.MATCH_ANY)
    void endingInEd(Status s) {
        assertTrue(s.name().endsWith("ED"));
    }
}

go deeper

for a junior

Knows @EnumSource runs the test once per enum constant and that the name attribute changes the report label.

for a middle

Filters constants with names/mode (INCLUDE/EXCLUDE/MATCH_*) and writes useful name templates with {index} and {0}.

for a senior

Uses regex modes deliberately, knows the 1-based index vs 0-based argument distinction, configures default display names, and labels opaque args with Named.of.

for a principal

Establishes report-readability conventions across the suite and uses enum-exhaustive tests to guard against unhandled new constants as a design safeguard.

## @EnumSource — iterate an enum's constants An **enum** in Java is a fixed set of named constants (e.g. `enum Status { ACTIVE, SUSPENDED, CLOSED }`). `@EnumSource` runs a `@ParameterizedTest` **once per constant**, injecting the constant into a method parameter declared as that enum type: ```java @ParameterizedTest @EnumSource(Status.class) void everyStatusHasLabel(Status s) { assertNotNull(s.label()); } ``` If the parameter type already *is* the enum, you may omit `Status.class` and JUnit infers it. ### Filtering constants Two attributes shape *which* constants are used: - `names` — a list of constant names. - `mode` — how `names` is interpreted: - `INCLUDE` (default) — use only the listed constants. - `EXCLUDE` — use all *except* the listed ones. - `MATCH_ALL` — `names` are regexes; a constant is used only if it matches **all** of them. - `MATCH_ANY` — used if it matches **any** regex. Examples: ```java @EnumSource(value = Status.class, names = {"ACTIVE", "SUSPENDED"}) // INCLUDE @EnumSource(value = Status.class, names = "CLOSED", mode = Mode.EXCLUDE) // all but CLOSED @EnumSource(value = Status.class, names = ".*ED$", mode = Mode.MATCH_ANY) // regex ``` This is the idiomatic way to assert behavior "for every status" (and catch the case where someone adds a new constant but forgets to handle it). ## The name template — display names Every `@ParameterizedTest` invocation gets a **display name**, controlled by the `name` attribute, which is a template string with **placeholders** resolved per invocation: - `{index}` — the **1-based** invocation number. - `{arguments}` — a comma-separated list of all arguments. - `{argumentsWithNames}` — arguments prefixed with their parameter names. - `{0}`, `{1}`, … — the argument at that **zero-based** position. ```java @ParameterizedTest(name = "[{index}] status={0}") @EnumSource(Status.class) void t(Status s) { ... } // → "[1] status=ACTIVE", "[2] status=SUSPENDED", ... ``` The default template (`DEFAULT_DISPLAY_NAME`) is roughly `"[{index}] {argumentsWithNames}"`. A good custom template turns an opaque report row into a sentence you can read at a glance, which matters when a single failing input among dozens needs to be identified fast. You can also set a project-wide default via the `junit.jupiter.params.displayname.default` configuration parameter. ### Naming opaque arguments When an argument's `toString()` is unhelpful (e.g. a big object), wrap it with `Named.of("label", value)` (in `@MethodSource` data) so `{0}` shows the label instead of the object's default string. ## Orthogonality Keep the two ideas separate in your head: **the source** (`@EnumSource`, `@ValueSource`, …) decides *which data* drives the invocations; **the name template** decides *how each invocation is labelled* in the report. Changing one never changes the other. ## Gotchas - `{index}` is 1-based, but positional `{0}` is 0-based — a common confusion. - A regex in `MATCH_ALL`/`MATCH_ANY` that matches nothing yields zero invocations (often a silent surprise). - `names` with `INCLUDE` referencing a constant that doesn't exist is an error — good, it fails fast.

  • How do you test all enum constants except one?
    Use @EnumSource(value = MyEnum.class, names = "THE_ONE", mode = Mode.EXCLUDE). EXCLUDE runs every constant not listed in names.
  • Is {index} zero-based or one-based, and what about {0}?
    {index} is one-based (first invocation is 1). The positional placeholders {0}, {1}, ... are zero-based argument indices. Mixing them up is a common slip.

saying these in an interview costs you the question

  • Thinking @EnumSource needs an explicit class even when the parameter type is the enum
  • Confusing INCLUDE vs EXCLUDE semantics
  • Assuming {0} is the index rather than the first argument
  • Forgetting MATCH_ALL/MATCH_ANY treat names as regexes, not literals

context