Compare @ValueSource and @CsvSource. When would you choose each, and how do they map to test method parameters?
answer
- ValueSource = one column, one parameter, no null
- CsvSource = rows → many params, positional
- CsvSource does implicit string→type conversion
- Quoting, delimiter, nullValues, emptyValue
- CsvFileSource = same rows from a classpath file
basics
~10 s@ValueSource gives a single column of values to a one-parameter method. @CsvSource gives comma-separated rows, so each row maps to several parameters. Use @ValueSource for one input; use @CsvSource when you need input-plus-expected pairs.
solid answer
~50 s@ValueSource supplies a single array of literals of one type (e.g. strings = {"a","b"} or ints = {1,2}). The test method takes exactly one matching parameter, and each literal becomes one invocation. It cannot express multiple arguments or null. @CsvSource supplies a list of comma-separated string rows; each row is split into fields that map positionally to multiple parameters, with JUnit's implicit conversion turning the string fields into the declared types (int, enum, LocalDate, etc.). That makes @CsvSource the natural choice for input/expected pairs like {"4, 16", "5, 25"}. @CsvSource also supports quoting (to embed commas or spaces), a custom delimiter, an emptyValue, and nullValues to represent null. For larger tables, @CsvFileSource reads the same rows from a classpath CSV file. Rule of thumb: one column of inputs → @ValueSource; rows of related values → @CsvSource.
code
java · 27 linesimport org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class SourcesDemo {
// one column → one parameter
@ParameterizedTest
@ValueSource(strings = {"radar", "level", "noon"})
void singleColumn(String word) {
assertEquals(word, new StringBuilder(word).reverse().toString());
}
// rows → multiple parameters, with conversion + a null
@ParameterizedTest
@CsvSource({
"2, 4",
"3, 9",
"'10', 100",
"NULL, 0"
})
void rows(String base, int expected) {
int b = base == null ? 0 : Integer.parseInt(base);
assertEquals(expected, b * b);
}
}go deeper
Knows @ValueSource is a single list and @CsvSource is comma-separated rows mapped to several parameters.
Maps fields positionally, explains implicit string-to-type conversion, and picks @CsvSource for input/expected pairs and @ValueSource for single columns.
Uses quoting, custom delimiters, nullValues/emptyValue, and @CsvFileSource for shared tables; knows @ValueSource can't express null and pairs it with @NullSource.
Sets conventions on inline tables vs externalized CSV vs MethodSource, balancing readability, conversion pitfalls, and maintainability across a large test suite.
## Why two annotations Both feed a `@ParameterizedTest`, but they differ in **shape of data**: a single column versus a table of rows. ## @ValueSource — one column `@ValueSource` declares **one array of literals of a single type**. Supported element kinds include `ints`, `longs`, `doubles`, `floats`, `chars`, `booleans`, `shorts`, `bytes`, `strings`, and `Class<?>` values: ```java @ParameterizedTest @ValueSource(ints = {1, 2, 4, 8}) void isPowerOfTwo(int n) { assertTrue((n & (n - 1)) == 0); } ``` The test method must take **exactly one parameter** whose type matches the element kind. Each literal triggers one invocation. Limitations: it carries **only one argument per invocation**, and it **cannot represent `null`** (annotation array elements can't be null). If you need null, use `@NullSource`/`@EmptySource`/`@NullAndEmptySource` (often *combined* with `@ValueSource`). ## @CsvSource — rows mapped to many parameters `@CsvSource` declares an array of **string rows**, each a comma-separated record. JUnit splits a row on the delimiter and maps the fields **positionally** to the method's parameters: ```java @ParameterizedTest @CsvSource({"2, 4", "3, 9", "5, 25"}) void squares(int base, int expected) { assertEquals(expected, base * base); } ``` Here `base` and `expected` come from columns 0 and 1. The fields arrive as *strings*; JUnit applies **implicit argument conversion** to coerce them into the declared parameter types — primitives, wrappers, enums (by name), `BigDecimal`, `UUID`, `LocalDate`/`LocalDateTime` (ISO format), `File`/`Path`, and more. This is why `@CsvSource` is the idiomatic way to express **input → expected** pairs. ### Useful @CsvSource attributes - `delimiter` / `delimiterString` — change the separator (e.g. `'|'`). - Quoting with single quotes `'...'` — embed commas/leading-trailing spaces: `"'Hello, World', 12"`. - `nullValues = {"NULL"}` — turn the token `NULL` into a real `null`. - `emptyValue` — what an empty field means; by default an empty unquoted field is `null`, and `''` is an empty string. - `useHeadersInDisplayName` — first row becomes column headers for nicer display names. ## @CsvFileSource — externalized rows When the table is large or shared, `@CsvFileSource(resources = "/data/cases.csv")` reads the identical row format from a CSV file on the classpath (`numLinesToSkip = 1` to skip a header). The mapping/conversion rules are the same as `@CsvSource`. ## Choosing between them | Need | Use | |---|---| | One column of single values | `@ValueSource` | | Each case has several related fields (input + expected) | `@CsvSource` | | Many rows / data reused across tests | `@CsvFileSource` | | Need to include `null` | `@NullSource` (often with `@ValueSource`) or `nullValues` on `@CsvSource` | | Arbitrary objects, not just literals/strings | `@MethodSource`/`@ArgumentsSource` | ## Gotchas - `@ValueSource` can't do multi-arg or null; reaching for it for pairs is a smell — use `@CsvSource`. - A `@CsvSource` field count mismatch with the parameter list causes an error; trailing empty fields default to null. - Whitespace around `@CsvSource` fields is trimmed by default; quote to preserve it.
- How do you pass a null value with @ValueSource?You can't directly — annotation array elements cannot be null. Combine @ValueSource with @NullSource (or use @NullAndEmptySource), or switch to @CsvSource with nullValues, or @MethodSource.
- How does a @CsvSource string field become an int or LocalDate parameter?Through JUnit's implicit argument conversion: the framework parses the string into the declared parameter type (primitives, enums by name, java.time types in ISO format, etc.).
saying these in an interview costs you the question
- Saying @ValueSource can supply multiple arguments per invocation
- Trying to pass null via @ValueSource
- Believing @CsvSource keeps fields as String when the parameter is typed (it converts)
- Mismatching the number of CSV columns and method parameters