skip to content

A JUnit 5 parameterized test method mixes plain indexed arguments, an ArgumentsAccessor or @AggregateWith parameter, and a TestInfo injected by the framework. What ordering rules apply to those parameters, and what happens if you get the order wrong?

level: seniorimportance: should knowfreq 24%

answer

  1. indexed → aggregators → resolver-supplied
  2. ArgumentsAccessor and @AggregateWith both count as aggregators
  3. TestInfo/TestReporter/TempDir go last
  4. violation = zero invocations, config error
  5. accessor indices stay absolute

basics

~20 s

Order is fixed: indexed arguments first, then aggregator parameters (ArgumentsAccessor or @AggregateWith), then anything supplied by an extension such as TestInfo or TestReporter. Wrong order fails the method up front with an invalid-parameter-order error, not a per-row failure.

solid answer

~50 s

JUnit Jupiter resolves a `@ParameterizedTest` method's parameters in three fixed groups: 1. **Indexed arguments** — bound positionally from the argument row. 2. **Aggregators** — any parameter of type `ArgumentsAccessor` or annotated `@AggregateWith`. These consume no column; each sees the entire row. 3. **`ParameterResolver`-supplied parameters** — `TestInfo`, `TestReporter`, `RepetitionInfo`, Spring/Mockito-injected parameters, and so on. So `void t(String id, ArgumentsAccessor args, TestInfo info)` is legal; `void t(ArgumentsAccessor args, String id)` is not. A violation is a *configuration* error: JUnit validates the signature when building the method context and fails with a message saying aggregators must be declared after indexed arguments and before parameters resolved by another `ParameterResolver` — the test never runs a row. The practical consequence is that a resolver-supplied parameter cannot sit in the middle. If it does, JUnit tries to bind an argument to it (or refuses), and you get either an arity mismatch or the ordering error rather than the injection you expected.

go deeper

for a junior

Just remember the sequence: data-bound parameters first, ArgumentsAccessor/@AggregateWith next, TestInfo-style injected parameters last.

for a middle

Explain why the rule exists — positional binding would otherwise be ambiguous — and that aggregators consume no column.

for a senior

Use it diagnostically: zero invocations plus a configuration message means signature, not data; a mid-signature TestInfo produces a misleading arity error.

for a principal

Point at the design smell behind long mixed signatures and set a convention (indexed columns you assert on, accessor for the tail, injected collaborators at class level).

## Three kinds of parameters, one fixed order A `@ParameterizedTest` method's formal parameters are not all fed from the same place. Jupiter classifies each one: - **Indexed** — an ordinary parameter that takes the next value from the argument row supplied by `@CsvSource`, `@MethodSource`, `@ValueSource`, and friends. Position *n* in this group takes value *n* of the row. - **Aggregator** — a parameter whose type is `ArgumentsAccessor`, or which is annotated (directly or via a composed annotation) with `@AggregateWith`. It consumes no value from the row; instead it is given a view over the whole row. - **Resolver-supplied** — a parameter that some registered `ParameterResolver` extension handles: `TestInfo`, `TestReporter`, `RepetitionInfo`, temp-dir paths, Spring beans, Mockito `@Mock` parameters, and anything a custom extension resolves. JUnit requires them in exactly that sequence: indexed, then aggregators, then resolver-supplied. `void test(String id, int qty, ArgumentsAccessor all, TestInfo info)` is valid. `void test(ArgumentsAccessor all, String id)` and `void test(String id, TestInfo info, int qty)` are not. ## Why the rule exists Binding is positional and the three groups are counted differently. Indexed parameters consume row values in order; aggregators consume none but must be told where the indexed run stops; resolver parameters consume none either but are matched by extensions. Without a fixed order, a signature like `(String, ArgumentsAccessor, String)` would be ambiguous — does the trailing `String` take row value 1 or row value 2? The ordering rule removes the ambiguity: everything before the first aggregator is indexed, and nothing after an aggregator may be indexed. Note that the aggregator's own indices are unaffected by how many indexed parameters precede it. In `void t(String id, ArgumentsAccessor args)` with row `"A, 1, 2"`, `args.get(0)` is still `"A"`. The accessor is a view over the complete row, not over the leftovers. ## What failure looks like A violation is detected while JUnit builds the parameterized-method context — before any invocation. The test does not fail once per row; the whole method fails with a single error whose message states that argument aggregators must be declared after any indexed arguments and before any arguments resolved by another `ParameterResolver`. In an IDE this shows up as the method being red with zero invocations, which is the important diagnostic clue: a per-row failure points at your data or code, a zero-invocation failure points at the signature or the source configuration. A related and more confusing symptom appears when a resolver-supplied parameter is placed in the middle of indexed ones: Jupiter counts it as indexed and you get an arity or conversion complaint about the row being too short or a value not converting to `TestInfo`, which sends people hunting in the CSV instead of the signature. ## Multiple aggregators More than one aggregator parameter is allowed, and they must all sit in the middle group. Each receives the same complete row. Splitting a row into an input object and an expected-result object with two aggregators is the standard use of this, and it is why the rule is phrased as "aggregators" plural rather than "the aggregator". ## Practical guidance - Read a failing signature left to right and mentally label each parameter I (indexed), A (aggregator), R (resolver). The labels must be non-decreasing in that order. - If you need `TestInfo` or a `TempDir`, push it to the end — always. - If a method needs many resolver-supplied collaborators plus a wide row, that is usually a signal to move the collaborators into fields injected at the class level and keep the method signature about the data. - Because the rule is validated per method, it is cheap to verify: run the single method; if it reports zero invocations with a configuration error, the signature is the suspect, not the data source. ## Interaction with the parameter count The number of indexed parameters need not equal the row's arity: a row may supply more values than there are indexed parameters, and the extras are simply not bound (they remain visible to an aggregator). Supplying *fewer* values than indexed parameters is an error. Combining that with the ordering rule explains a frequent pattern: declare the two or three columns you assert on as indexed parameters for readability, add an `ArgumentsAccessor` at the end for the long tail of extra columns, and keep any `TestInfo` last of all.

  • How do you tell an ordering mistake apart from a bad data row when a parameterized test fails?
    Look at the invocation count. An ordering or signature problem is validated before execution, so the method reports a single configuration failure with zero invocations. A bad row fails only the invocations affected and the others still run and pass. That distinction is the fastest triage step for parameterized-test failures.
  • Can an argument row supply more values than the method has indexed parameters?
    Yes — surplus values are simply not bound to parameters, though an ArgumentsAccessor or aggregator still sees them. Supplying fewer values than there are indexed parameters is an error. This is why a common layout is a couple of indexed parameters for the values you assert on plus an accessor for the rest.

saying these in an interview costs you the question

  • Putting TestInfo or a @TempDir parameter in the middle and blaming the CSV when binding fails
  • Believing the ordering error appears per row rather than as a single method-level configuration failure
  • Assuming only one aggregator parameter is permitted
  • Thinking indexed parameters after an aggregator will pick up the remaining columns

context