skip to content

A JUnit 5 parameterized test reports invocations as "[1] arg0=admin, arg1=403" instead of using the real parameter names. Explain what the {arguments} and {argumentsWithNames} placeholders each produce and why the names degraded to arg0/arg1.

level: middleimportance: should knowfreq 35%

answer

  1. {arguments} = values only
  2. {argumentsWithNames} = name=value
  3. names need -parameters at compile time
  4. no flag → arg0, arg1 from reflection
  5. Named.of / positional {0} avoid the dependency

basics

~20 s

{arguments} prints the argument values comma-separated via toString. {argumentsWithNames} prefixes each with its formal parameter name. Parameter names only survive in the class file when compiled with the -parameters flag; without it reflection reports arg0, arg1, so the label degrades.

solid answer

~50 s

`{arguments}` renders the invocation's arguments as a comma-separated list of `String.valueOf` results: `admin, 403`. `{argumentsWithNames}` renders the same values but prefixed with each formal parameter name: `role=admin, status=403`. Since JUnit 5.8 the named form is the default invocation-name pattern. The parameter names come from reflection (`java.lang.reflect.Parameter#getName`). The JVM only stores real names in the `MethodParameters` class-file attribute when the code is compiled with the `-parameters` flag; otherwise reflection synthesises `arg0`, `arg1`, … and that is exactly what lands in the report. So `arg0=admin` is a compilation setting symptom, not a JUnit bug — enable `-parameters` for the test compilation and the labels fix themselves. If you cannot or do not want to rely on the flag, you can name values explicitly by wrapping them in `Named.of("label", value)` from an argument provider, or write a positional pattern such as `name = "{0} -> {1}"`, which never depends on parameter names at all.

code

java · 8 lines
java
@ParameterizedTest(name = "{arguments}")            // admin, 403
@ParameterizedTest(name = "{argumentsWithNames}")   // role=admin, status=403
@ParameterizedTest(name = "role {0} yields HTTP {1}") // role admin yields HTTP 403

// value carries its own label, independent of toString()
static Stream<Arguments> cases() {
    return Stream.of(Arguments.of(Named.of("empty cart", new Cart()), 0));
}

go deeper

for a junior

Say what each placeholder prints and that the named form is the default; recognising arg0 as 'names not compiled in' is enough.

for a middle

Explain the MethodParameters class-file attribute and the -parameters flag, and that reflection synthesises argN without it.

for a senior

Treat argN as a repo-wide build-hygiene fix, and reach for Named.of or positional patterns so labels do not depend on compiler flags or a fat toString().

for a principal

Argue for a suite-wide convention: labels are report API consumed by CI and flake tooling, so they should be stable and value-shaped rather than incidental toString() output.

## Two placeholders, one value list Both placeholders describe the *same* arguments of a single invocation; they differ only in decoration. `{arguments}` is the plain form. JUnit takes the argument array for that invocation, converts each element with `String.valueOf` (so `null` becomes the four letters `null`, and any object goes through its own `toString()`), and joins them with `, `. Output: `admin, 403`. `{argumentsWithNames}` is the annotated form: each element is emitted as `name=value`, where the name is the formal parameter name of the test method's corresponding parameter. Output: `role=admin, status=403`. It is the default since JUnit 5.8, when `DEFAULT_DISPLAY_NAME` changed from `"[{index}] {arguments}"` to `"[{index}] {argumentsWithNames}"`. ## Where the names come from — and why they vanish Java does not keep formal parameter names in bytecode by default. They live in an optional class-file attribute called `MethodParameters`, emitted only when the compiler is given the `-parameters` flag. Without it, `Parameter#getName()` returns synthetic placeholders `arg0`, `arg1`, `arg2` — and since JUnit simply asks reflection for the name, the report inherits them. This is why a suite can print perfect labels on one developer's machine and `arg0=` in CI: the flag is set for one compilation and not the other. Many modern setups turn it on (it is also what Spring needs for constructor/parameter binding), but it is never automatic. Note the flag must be applied to the compilation of the **test** sources — enabling it only for main sources changes nothing about test parameter names. ## Escaping the dependency entirely Three ways to get readable labels without relying on `-parameters`: 1. **Positional pattern.** `@ParameterizedTest(name = "role {0} yields HTTP {1}")` reads better than any automatic form and cannot degrade, because it never consults parameter names. It is the pragmatic default for tests with two or three arguments. 2. **Named values.** Wrapping a value as `Named.of("empty cart", cart)` in an argument provider makes the *value* carry its own label, which is what appears in `{arguments}`/`{argumentsWithNames}`. This is the right tool when the value's own `toString()` is meaningless (a builder-built domain object, a lambda, a byte array). 3. **A readable `toString()`.** Since both placeholders funnel through `String.valueOf`, giving the argument type a compact `toString()` improves every report line at once — and conversely, a record or Lombok `@ToString` that dumps twenty fields makes every invocation label unusable. ## Practical guidance Prefer `{argumentsWithNames}` (or the default) for tests with several arguments of the same type, where `admin, 403, false` is ambiguous and `role=admin, status=403, retry=false` is not. Prefer an explicit positional pattern when you want the label to read as a sentence describing the behaviour. And treat `arg0=` appearing in a report as a build-hygiene signal worth fixing once for the whole repository, because it also silently affects other reflection-based tooling.

  • An argument is a domain object whose toString() prints thirty fields, making every invocation label unreadable. What are your options?
    Either stop letting the object's toString() drive the label — wrap it as Named.of("short label", object) in the provider, or switch to a positional pattern that prints only a discriminating field such as {0} plus a literal. Newer JUnit versions also truncate over-long arguments in display names via a configuration parameter, but truncation only hides the noise; naming the case describes it.
  • Does {argumentsWithNames} show the names of the test method's parameters or the names used in the argument source?
    The test method's formal parameter names, obtained by reflection on the method. The argument source supplies only values; it has no say in the names unless you wrap a value in Named, in which case the value's own label is what gets rendered.

saying these in an interview costs you the question

  • Claiming argN is a JUnit bug rather than a missing -parameters compile flag.
  • Thinking {argumentsWithNames} reads names from the @CsvSource/@MethodSource declaration.
  • Enabling -parameters only for main sources and expecting test labels to change.
  • Assuming a huge object toString() is JUnit's problem rather than the argument type's.

context