skip to content

Compare @ValueSource and @CsvSource. When would you choose each, and how do they map to test method parameters?

level: middleimportance: must knowfreq 62%

answer

  1. ValueSource = one column, one parameter, no null
  2. CsvSource = rows → many params, positional
  3. CsvSource does implicit string→type conversion
  4. Quoting, delimiter, nullValues, emptyValue
  5. 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 lines
java
import 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

for a junior

Knows @ValueSource is a single list and @CsvSource is comma-separated rows mapped to several parameters.

for a middle

Maps fields positionally, explains implicit string-to-type conversion, and picks @CsvSource for input/expected pairs and @ValueSource for single columns.

for a senior

Uses quoting, custom delimiters, nullValues/emptyValue, and @CsvFileSource for shared tables; knows @ValueSource can't express null and pairs it with @NullSource.

for a principal

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

context