JUnit Parameterized Tests

JUnit parameterized tests execute the same test method repeatedly with different argument sets. In JUnit 5, annotations such as `@ValueSource`, `@CsvSource`, and `@MethodSource` provide those inputs while keeping the test logic in one place.

A parameterized test is declared with `@ParameterizedTest` instead of `@Test`. Each invocation receives arguments from a configured source, so one test method can verify the same behavior across many inputs without duplicating its body.

`@ValueSource` is convenient for a single primitive or `String` argument. `@CsvSource` supplies multiple arguments per invocation using comma-separated rows, while `@MethodSource` obtains arguments from a method and is useful for richer or dynamically constructed test data.

A source must provide values compatible with the test method's parameters, either directly or through JUnit's supported argument conversion mechanisms. For `@MethodSource`, a factory commonly returns a `Stream`, `Iterable`, array, or another supported source type.

Parameterized tests improve coverage and make edge cases explicit, but they do not automatically make tests better. The input cases should represent meaningful equivalence classes, boundaries, invalid values, and other behaviors relevant to the code under test.

Key points

Interview questions

What problem do JUnit parameterized tests solve?

They let one test method run against multiple input cases. This avoids copying the same assertions into separate test methods when only the data changes. Each supplied argument set creates a separate invocation of the parameterized test.

When would you choose `@ValueSource`, `@CsvSource`, or `@MethodSource`?

I would use `@ValueSource` for simple cases involving one argument. `@CsvSource` is useful when each case can be represented compactly as literal columns. I would choose `@MethodSource` when the data is complex, needs computation, uses objects, or should be shared or generated by Java code.

How does `@MethodSource` work?

`@MethodSource` points a parameterized test at a factory method that supplies its arguments. The factory can produce supported containers such as a `Stream` of arguments, with each element representing an invocation's input. This is useful when constructing arguments requires Java expressions rather than literal annotation values.

What makes a good parameterized test dataset?

The dataset should represent distinct behavioral cases rather than arbitrary volume. I would include normal inputs, boundaries, invalid inputs, and cases that correspond to meaningful equivalence classes. The goal is to expose different behavior while keeping each invocation understandable and failures diagnosable.