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?
answer
- @ParameterizedTest + @ValueSource
- one value per invocation
- exactly one attribute set
- constants only — no null, no objects
- widening + String conversion apply
basics
~20 sMark 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 sReplace `@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@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 allowedgo deeper
Show the annotation pair, say one value per invocation, and name the no-null / single-attribute limits.
Add widening and implicit String conversion, and explain that annotation constants are the reason the source is limited.
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.
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).