skip to content

You need a JUnit 5 test to run once per constant of a Java enum, and later to run over only a named subset of those constants. How do you declare that, and how do you express both 'only these' and 'all except these'?

level: middleimportance: should knowfreq 45%

answer

  1. @EnumSource — one invocation per constant
  2. class inferable from parameter (5.6+)
  3. names + mode: INCLUDE default / EXCLUDE
  4. MATCH_ALL / MATCH_ANY = regex on name()
  5. INCLUDE/EXCLUDE validate names; regex does not

basics

~20 s

Use @ParameterizedTest with @EnumSource. With no attributes it runs over every constant of the enum parameter's type. Restrict with names plus mode: INCLUDE (default) runs only the listed constants, EXCLUDE runs all others, and MATCH_ALL / MATCH_ANY treat the names as regular expressions.

solid answer

~50 s

`@EnumSource` supplies enum constants as arguments: ```java @ParameterizedTest @EnumSource(Status.class) // every constant void handlesAll(Status status) { } ``` Since JUnit 5.6 the enum class can be omitted when the test's parameter type is the enum itself — `@EnumSource` alone is enough. Subsetting uses `names` together with `mode`: - `INCLUDE` (default): `@EnumSource(names = {"OPEN", "CLOSED"})` — only those constants. - `EXCLUDE`: `@EnumSource(mode = EXCLUDE, names = {"UNKNOWN"})` — everything else. - `MATCH_ALL` / `MATCH_ANY`: `names` are **regular expressions**, e.g. `mode = MATCH_ALL, names = "^DRAFT_.*"`. The distinction matters for safety. With `INCLUDE`/`EXCLUDE`, a name that matches no constant is a configuration error, so a typo or a renamed constant fails loudly. Regex modes have no such guarantee — a pattern that matches nothing yields zero invocations, which JUnit rejects as an error by default, but a pattern that matches *fewer* constants than intended silently narrows coverage.

code

java · 17 lines
java
enum Status { OPEN, IN_REVIEW, CLOSED, ARCHIVED }

@ParameterizedTest
@EnumSource                                   // type inferred, all constants
void all(Status status) { }

@ParameterizedTest
@EnumSource(names = {"OPEN", "IN_REVIEW"})    // mode = INCLUDE (default)
void onlyLive(Status status) { }

@ParameterizedTest
@EnumSource(mode = EnumSource.Mode.EXCLUDE, names = "ARCHIVED")
void everythingElse(Status status) { }

@ParameterizedTest
@EnumSource(mode = EnumSource.Mode.MATCH_ALL, names = "^IN_.*$")
void intermediate(Status status) { }

go deeper

for a junior

Show @EnumSource over all constants and the names attribute for a subset; knowing INCLUDE is the default is enough.

for a middle

Cover all four modes, that regex modes match against name(), and that the enum class can be inferred from the parameter.

for a senior

Lead with the validation asymmetry — INCLUDE/EXCLUDE fail loudly on renames, regex modes silently narrow — and pick the mode by which failure you want.

for a principal

Treat enum-driven tests as a coverage contract with the type: choose the mode that makes adding or renaming a constant surface in CI rather than pass quietly.

## Running once per constant `@EnumSource` is the argument source for enums. In its simplest form: ```java enum Status { OPEN, IN_REVIEW, CLOSED, ARCHIVED } @ParameterizedTest @EnumSource void everyStatusIsHandled(Status status) { assertDoesNotThrow(() -> handler.handle(status)); } ``` The engine produces one invocation per declared constant, in declaration order, passing the constant itself. The enum type is taken from the annotation's `value` attribute if given (`@EnumSource(Status.class)`); since JUnit 5.6 it is inferred from the test method's single parameter when omitted. Being explicit is still common in codebases where the parameter is declared as a supertype or where readability matters more than brevity. ## Subsetting: names + mode Two attributes cooperate. `names` is a `String[]`; `mode` is a `Mode` enum that decides how those strings are interpreted: - **`INCLUDE`** — the default. Only constants whose names appear in the list are supplied. - **`EXCLUDE`** — every constant *except* the listed ones. - **`MATCH_ALL`** — names are regular expressions; a constant is selected only if it matches **all** of them. - **`MATCH_ANY`** — names are regular expressions; a constant is selected if it matches **at least one**. - **`MATCH_NONE`** (newer JUnit 5 versions) — the regex complement: constants matching none of the patterns. ```java @ParameterizedTest @EnumSource(mode = EXCLUDE, names = {"ARCHIVED"}) void liveStatusesAreEditable(Status status) { } @ParameterizedTest @EnumSource(mode = MATCH_ALL, names = "^IN_.*$") void intermediateStatuses(Status status) { } ``` Regex modes match against the constant's `name()`, and the whole name must match the pattern. ## The validation difference that matters With `INCLUDE` and `EXCLUDE`, JUnit validates the supplied names against the enum's actual constants. A name that does not exist — a typo, or a constant someone renamed — makes the test fail with a configuration error naming the bad entry. That is a genuinely useful property: refactoring the enum cannot silently detach the test from it. Regex modes give up that safety. Patterns are not validated against anything; they simply select whatever they select. If a rename turns `IN_REVIEW` into `UNDER_REVIEW`, a `^IN_.*` pattern quietly stops matching it. When nothing at all matches, the test produces zero invocations, which JUnit treats as an error by default (recent versions expose an `allowZeroInvocations` opt-out on `@ParameterizedTest`) — but partial silent narrowing is the real hazard, and it produces a green build with reduced coverage. So: prefer `INCLUDE`/`EXCLUDE` for small, explicit sets; reserve regex modes for enums with a genuine naming convention (`ERROR_*`, `*_DEPRECATED`) where the convention is itself the selection criterion. ## Reporting Invocation labels render the constant via `toString()`, so with default settings a run reads `[2] status=IN_REVIEW`. That readability is part of why `@EnumSource` is preferred over listing constant names as strings in a literal source: the argument is the real enum value, type-checked at compile time and renamed automatically by IDE refactoring, rather than a string that drifts. ## Where it fits `@EnumSource` covers the 'same behaviour for each constant' shape. As soon as each constant needs a *different* expected result, you are back to argument tuples and a different source — pairing a constant with its expectation is not something `@EnumSource` can express, and forcing it leads to a `switch` inside the test body, which is a test that asserts its own copy of the production mapping. ## How to answer Show the bare form, then `names` + the four modes, and volunteer the validation asymmetry between the literal modes and the regex modes. That last point is the one that distinguishes usage from understanding.

  • What happens if a name listed under mode = INCLUDE does not correspond to any constant of the enum?
    JUnit fails the test with a configuration error identifying the invalid name. INCLUDE and EXCLUDE validate their names against the enum's declared constants, so a typo or a renamed constant is caught loudly. Regex modes perform no such validation and would simply select nothing.
  • Each enum constant needs a different expected outcome. Is @EnumSource still the right source?
    No. @EnumSource supplies one argument — the constant — and cannot pair it with an expectation. Encoding the expectation with a switch inside the test body just duplicates the production mapping in the test. Move to a source that yields argument tuples so constant and expected value travel together.

saying these in an interview costs you the question

  • Believing names is always a regex list, regardless of mode.
  • Thinking EXCLUDE is the default when names is present (INCLUDE is).
  • Assuming a regex that matches nothing is reported as a failure of intent — it is only caught because zero invocations is an error.
  • Claiming the enum class attribute is always mandatory (inferable from the parameter since 5.6).
  • Putting a switch on the constant inside the test body and calling it parameterized.

context