A JUnit 5 parameterized test is fed rows of twelve columns and you don't want a twelve-parameter test method. What does declaring a parameter of type ArgumentsAccessor give you, and how do you read values out of it?
answer
- one parameter, whole row
- zero-based, absolute indices
- getString(0) / get(3, LocalDate.class)
- typed getters convert, not cast
- size() / toList() / toArray()
basics
~20 sDeclare one parameter of type ArgumentsAccessor. JUnit passes it the whole argument array for that invocation instead of consuming a single column. You read values positionally: getString(0), getInteger(3), get(5, LocalDate.class), plus size(), toList() and toArray().
solid answer
~50 s`ArgumentsAccessor` (package `org.junit.jupiter.params.aggregator`) is a special parameter type on a `@ParameterizedTest` method. It is not bound to one column: JUnit gives it access to **all** arguments of the current invocation, so a twelve-column row becomes one parameter instead of twelve. You read positionally, zero-based: - `accessor.getString(0)`, `getInteger(1)`, `getBoolean(2)`, `getLong`, `getDouble`, `getCharacter`, … for common types; - `accessor.get(4, LocalDate.class)` for anything else — it runs the same implicit argument conversion JUnit would run for an indexed parameter; - `accessor.get(0)` for the raw `Object`; `size()`, `toArray()`, `toList()` to iterate. Indices are absolute over the whole row, so you may still declare indexed parameters before the accessor without the indices shifting. The cost is readability: positional indices are unnamed, and a bad index or an unconvertible value only fails at runtime (`ArgumentAccessException` for a failed typed read). When the same shape repeats across tests, promote it to an `ArgumentsAggregator`.
code
java · 19 lines@ParameterizedTest
@CsvSource({
"Jane, Doe, F, 1990-05-20, 3",
"John, Roe, M, 1985-11-02, 0"
})
void buildsPersonFromRow(ArgumentsAccessor args) {
assertEquals(5, args.size());
String first = args.getString(0);
String last = args.getString(1);
Gender gender = args.get(2, Gender.class);
LocalDate dob = args.get(3, LocalDate.class);
int children = args.getInteger(4);
Person person = new Person(first, last, gender, dob, children);
assertEquals(first + " " + last, person.fullName());
assertTrue(person.dateOfBirth().isBefore(LocalDate.now()));
}go deeper
Know that one ArgumentsAccessor parameter stands for the whole row and that you read it positionally with getString(0), getInteger(1) and so on.
Add the details: absolute zero-based indices even when mixed with indexed parameters, get(index, Class) performing implicit conversion, size()/toList(), and the runtime failure modes.
Frame it as a readability tradeoff — compile-time parameter checking traded for flexibility — and say when you would promote the extraction into an ArgumentsAggregator instead.
Talk about test-data design: how wide rows signal a missing domain object or an over-broad test, and set a team convention for when accessors are acceptable versus when data should be modelled.
## The problem it solves A `@ParameterizedTest` normally binds arguments positionally to formal parameters: the first value of the row goes to the first parameter, the second to the second, and so on. That is pleasant for two or three columns and awful for ten — the signature becomes a wall of `String a, String b, int c, …` that nobody can read, and adding a column means editing every signature. `ArgumentsAccessor` is JUnit Jupiter's escape hatch. It lives in `org.junit.jupiter.params.aggregator` and is treated specially by the parameterized-test machinery: a parameter of that type does **not** consume one argument from the row. Instead JUnit constructs an accessor over the complete argument array for the current invocation and injects it. ## The API The accessor is a thin, read-only, positional view: - `Object get(int index)` — the raw value as the source produced it. - `<T> T get(int index, Class<T> requiredType)` — the value converted to `requiredType`. - Typed convenience getters: `getString`, `getBoolean`, `getByte`, `getCharacter`, `getShort`, `getInteger`, `getLong`, `getFloat`, `getDouble`. - `int size()`, `Object[] toArray()`, `List<Object> toList()`. Indices are zero-based and **absolute over the entire row**. This matters when you mix styles: in `void test(String name, ArgumentsAccessor args)` the first column is bound to `name` *and* is still visible as `args.get(0)`; the accessor's view does not shift to skip already-bound parameters. ## Conversion inside the accessor The typed getters and `get(index, Class)` are not plain casts. They delegate to the framework's default argument converter, which is the same implicit conversion applied to indexed parameters: a `String` is turned into the requested primitive, wrapper, enum, `UUID`, `Path`, `BigDecimal`, `java.time` type, and so on. So a CSV column `"2021-03-09"` can be read with `args.get(3, LocalDate.class)` and you get a parsed `LocalDate`, not a `ClassCastException`. If the value cannot be converted, the read throws `ArgumentAccessException` naming the index and the target type; an index outside `[0, size())` fails the invocation with a precondition error. ## Where it may be declared An `ArgumentsAccessor` parameter counts as an *aggregator* for the purpose of JUnit's parameter-ordering rule: indexed arguments first, aggregators next, extension-resolved parameters (`TestInfo`, `TestReporter`, and anything a `ParameterResolver` supplies) last. Declaring an accessor before an indexed parameter is a configuration error, and JUnit fails the method up front with a message about an invalid parameter order rather than producing confusing bindings. You may declare more than one accessor-style parameter; each one sees the same complete row, which is occasionally useful when two aggregators split a row into two objects. ## Typical use ```java @ParameterizedTest @CsvSource({"Jane, Doe, F, 1990-05-20"}) void createsPerson(ArgumentsAccessor args) { Person p = new Person(args.getString(0), args.getString(1), args.get(2, Gender.class), args.get(3, LocalDate.class)); assertEquals("Jane Doe", p.fullName()); } ``` Beyond wide rows, the accessor is the right tool when the row's *arity varies* between cases (`size()` lets you branch), when you want to feed the whole row into a builder, or when a test genuinely treats the arguments as a collection rather than as named concepts. ## The costs The accessor trades compile-time structure for flexibility, and you should say so in an interview: - Positional indices are unnamed. `args.getString(7)` tells a reader nothing; a comment, a set of `private static final int` column constants, or an aggregator restores meaning. - Type errors move from compile time to run time. A wrong `getInteger(2)` compiles fine and fails on every invocation. - Refactoring a column's position silently breaks every index after it, and no tool will catch it. That is why the usual advice is: keep indexed parameters while they are few and readable; reach for the accessor for one-off wide rows; and when the same row shape appears in several tests, wrap the extraction once in an `ArgumentsAggregator` and let the test methods take a real domain object instead. The accessor is then an implementation detail of the aggregator rather than something every test body deals with.
- If a test method declares both an indexed parameter and an ArgumentsAccessor, does the accessor see the argument already bound to that parameter?Yes. The accessor is built over the complete argument array for the invocation, so index 0 is the first value of the row regardless of how many indexed parameters precede the accessor. Nothing is consumed or shifted. That is a common source of off-by-one bugs for people who assume the accessor starts after the bound parameters.
- What happens if you call accessor.getInteger(2) on a column containing "abc"?The typed getter delegates to the default argument converter, which cannot parse "abc" as an int, so the read throws an ArgumentAccessException naming the index and target type and that invocation fails. It is a runtime failure only — the call compiles fine, which is exactly the safety you give up compared with an indexed int parameter.
- How would you keep positional reads readable in a large test class?Either declare named column constants (private static final int DOB = 3) and use them at every read site, or move the extraction into an ArgumentsAggregator so the test method takes a domain object and the index knowledge lives in exactly one place. The second option also lets several tests share the same row shape.
Indexed parameters are a destructuring assignment; the ArgumentsAccessor is being handed the array itself and indexing into it yourself.
saying these in an interview costs you the question
- Believing the accessor is bound to one column, or that its indices start after the preceding indexed parameters
- Thinking get(i, Type) is a cast, so being surprised that a String column becomes a LocalDate — or that a bad value throws ArgumentAccessException rather than ClassCastException
- Declaring the accessor before indexed parameters and expecting it to work
- Using the accessor everywhere by default, turning readable two-parameter tests into anonymous index soup