skip to content

@ValueSource & @EnumSource

The single-argument sources for literals and enum constants, plus the null/empty companions. Where every parameterized-test interview starts.

on this pageshow

questions

4

You want a JUnit 5 test method to run once for each of the literal inputs 1, 2 and 3 without writing a loop inside the test. Which annotations do you use, and what are the limitations of that argument source?

level: juniorimportance: must knowfreq 60%

answer

  1. @ParameterizedTest + @ValueSource
  2. one value per invocation
  3. exactly one attribute set
  4. constants only — no null, no objects
  5. widening + String conversion apply

basics

~20 s

Mark the method @ParameterizedTest and add @ValueSource(ints = {1, 2, 3}); it runs once per value, passing one argument each time. Limits: exactly one array attribute, only primitives plus String and Class, values must be compile-time constants, and null cannot be supplied.

solid answer

~50 s

Replace `@Test` with `@ParameterizedTest` and add `@ValueSource(ints = {1, 2, 3})`. JUnit runs the method three times, passing one value per invocation, and each run is reported separately — so a failure identifies the offending input instead of aborting a loop on the first bad case. The constraints follow from it being an annotation: - **One argument per invocation.** The method must take a single parameter (aside from injected ones such as `TestInfo`). For multiple arguments you need a different source. - **Exactly one attribute may be set** — `ints`, `strings`, `longs`, `doubles`, `floats`, `shorts`, `bytes`, `chars`, `booleans`, `classes`. Setting two, or none, is a configuration error. - **Compile-time constants only.** Annotation values cannot be computed, so no `LocalDate.now()`, no field references that are not constants. - **No `null`.** Annotation array elements cannot be null; `@NullSource` exists for that. When the data outgrows literals, move to a source that supplies argument tuples or computed values.

code

java · 13 lines
java
@ParameterizedTest
@ValueSource(strings = {"level", "rotor"})
void detectsPalindromes(String candidate) {
    assertTrue(Palindromes.isPalindrome(candidate));
}

@ParameterizedTest
@ValueSource(ints = {1, 2})   // widening: int values into a long parameter
void widens(long n) { }

// configuration errors:
// @ValueSource(ints = {1}, strings = {"a"})  -> more than one attribute
// @ValueSource(strings = {"a", null})        -> null not allowed

go deeper

for a junior

Show the annotation pair, say one value per invocation, and name the no-null / single-attribute limits.

for a middle

Add widening and implicit String conversion, and explain that annotation constants are the reason the source is limited.

for a senior

Frame the choice: parameterize when the same behaviour is exercised with different data; a branching test body or a twenty-literal list signals the wrong source or the wrong test split.

for a principal

Discuss test-data strategy — literal tables in the annotation for cheap boundary cases versus generated or provider-supplied data, and the readability/maintenance cost of each.

## The mechanism A plain `@Test` method runs once. `@ParameterizedTest` marks the method as a template: the Jupiter engine looks for one or more *argument source* annotations, asks each to produce a stream of argument sets, and runs the method once per set. Every run is an independent invocation with its own lifecycle — `@BeforeEach`/`@AfterEach` execute around each one — and its own entry in the report. `@ValueSource` is the simplest source: a single array of literals, each element becoming the sole argument of one invocation. ```java @ParameterizedTest @ValueSource(ints = {1, 2, 3}) void isPositive(int n) { assertTrue(n > 0); } ``` Three invocations, three report lines. Contrast the loop-inside-one-test alternative: a loop stops at the first failing element, hides the remaining cases, and reports one opaque failure. Parameterization keeps the cases independent — a real behavioural difference, not a style preference. ## What the annotation can carry `@ValueSource` declares one array attribute per supported type: `shorts`, `bytes`, `ints`, `longs`, `floats`, `doubles`, `chars`, `booleans`, `strings`, `classes`. That list is the whole menu, and it exists because Java annotations may only hold primitives, `String`, `Class`, enums, other annotations, and arrays of those. Exactly one attribute must be set. Setting `ints` and `strings` together is a configuration error, and so is setting none — JUnit fails the test rather than running zero invocations. (Newer JUnit 5 versions add an `allowZeroInvocations` opt-out on `@ParameterizedTest`, but the default remains: no arguments is a failure, because a silently empty test is worse than a red one.) Because annotation attributes are compile-time constants, everything dynamic is off the table: no `Instant.now()`, no values read from a file, no objects. A `static final` constant works; a computed field does not. ## Widening and conversion The declared array type does not have to match the parameter type exactly. JUnit applies widening primitive conversion, so `@ValueSource(ints = {1, 2})` can feed a `long` or `double` parameter. It also performs implicit conversion from `String` to many target types — `@ValueSource(strings = {"2024-05-01"})` can bind to a `LocalDate` parameter. Those conversion rules are their own subject; the point here is that `strings` is more flexible than it first looks and is often used to reach non-annotatable types. ## What it cannot do, and what to reach for instead Two cases break out of `@ValueSource` immediately: - **More than one argument per case** — e.g. input plus expected output. `@ValueSource` supplies exactly one value per invocation; you need a source that produces argument tuples. - **`null`, or values that must be constructed** — annotations cannot hold `null` or objects. `@NullSource`, `@EmptySource` and `@NullAndEmptySource` cover the null/empty edge cases declaratively; anything requiring construction needs a method- or provider-based source. A good instinct at review time: if a `@ValueSource` list has grown to twenty near-identical literals, or if the test body branches on the value (`if (input.equals("admin")) ...`), the parameterization has stopped being a table of cases and become a disguised loop with hidden conditionals. Cases should exercise the same behaviour with different data; different behaviours want different test methods. ## How to answer Show the two annotations, state 'one value per invocation', and list the three real constraints — single attribute, constants only, no null. Adding *why* it beats a loop (independent invocations, per-case reporting) is what separates a rehearsed answer from an understood one.

  • Why is @ParameterizedTest with @ValueSource better than looping over an array inside a single @Test?
    Each value becomes an independent invocation with its own lifecycle and its own report entry, so every failing case is visible instead of only the first one, and the report names the input that broke. A loop aborts at the first failed assertion, masking later cases, and produces a single opaque failure.
  • Your test needs an input and its expected output for each case. Why can @ValueSource not express that, and what changes?
    @ValueSource supplies exactly one argument per invocation, so it cannot express a tuple. You switch to a source that produces argument sets — a CSV-style source for literal pairs, or a method/provider-based source when values must be constructed. The @ParameterizedTest annotation and the per-invocation reporting stay identical; only the source changes.

saying these in an interview costs you the question

  • Thinking @ValueSource can supply several arguments per invocation.
  • Trying to pass null in the array instead of using @NullSource.
  • Setting two attributes (e.g. ints and strings) on one @ValueSource.
  • Expecting computed values like LocalDate.now() to work in an annotation.
  • Forgetting to swap @Test for @ParameterizedTest, so the method never receives arguments (or fails to run).

context

open as a page

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%

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.

open as a page

A JUnit 5 argument source built from annotation literals cannot supply null, yet null, empty string and blank string are exactly the inputs a validation method must reject. How do you cover all three in one parameterized test?

level: middleimportance: should knowfreq 40%

basics

~20 s

Stack the dedicated sources: @NullSource supplies null, @EmptySource supplies an empty value ("", empty list/set/map/array), @NullAndEmptySource does both, and @ValueSource(strings = {" ", "\t"}) adds blanks. Source annotations are repeatable and their arguments are concatenated.

open as a page

A team's JUnit 5 tests iterate over a Java enum but exclude two constants by name so the suite stays green. Six months later a new constant is added and nothing fails. What went wrong, and how would you set up enum-driven tests so a newly added constant cannot slip through untested?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Excluding by name means new constants are automatically included in the happy-path run but nothing forces anyone to think about them; if the excluded constants are the only ones asserted elsewhere, coverage silently drifts. Prefer exhaustive @EnumSource with no filtering, and pair the constant with its expectation so an unmapped constant fails.

open as a page