JUnit Jupiter extension for parameterized tests
—
Comprehensive CSV data support for inline data and external files with extensive parsing customization options.
Provides CSV data inline as test arguments with extensive parsing customization.
/**
* Provides CSV data inline as arguments to parameterized tests
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@ArgumentsSource(CsvArgumentsProvider.class)
@Repeatable(CsvSources.class)
@interface CsvSource {
/**
* Array of CSV records
*/
String[] value() default {};
/**
* CSV data as text block (Java 15+ syntax)
*/
String textBlock() default "";
/**
* Use first row as headers in display names
*/
boolean useHeadersInDisplayName() default false;
/**
* Quote character for escaping
*/
char quoteCharacter() default '\'';
/**
* Single character delimiter
*/
char delimiter() default ',';
/**
* Multi-character delimiter string (takes precedence over delimiter)
*/
String delimiterString() default "";
/**
* Value to use for empty quoted strings
*/
String emptyValue() default "";
/**
* Custom strings that represent null values
*/
String[] nullValues() default "";
/**
* Maximum characters per column (protection against malformed data)
*/
int maxCharsPerColumn() default 4096;
/**
* Whether to trim whitespace from unquoted values
*/
boolean ignoreLeadingAndTrailingWhitespace() default true;
}Usage Examples:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class CsvSourceExamples {
// Basic CSV data
@ParameterizedTest
@CsvSource({
"1, apple, true",
"2, banana, false",
"3, cherry, true"
})
void testBasicCsv(int id, String name, boolean active) {
assertTrue(id > 0);
assertNotNull(name);
}
// Custom delimiter
@ParameterizedTest
@CsvSource(value = {
"1|apple|red",
"2|banana|yellow",
"3|cherry|red"
}, delimiter = '|')
void testCustomDelimiter(int id, String fruit, String color) {
assertTrue(id > 0);
assertNotNull(fruit);
assertNotNull(color);
}
// Multi-character delimiter
@ParameterizedTest
@CsvSource(value = {
"apple::red::sweet",
"lemon::yellow::sour"
}, delimiterString = "::")
void testMultiCharDelimiter(String fruit, String color, String taste) {
assertNotNull(fruit);
assertNotNull(color);
assertNotNull(taste);
}
// Quoted values with commas
@ParameterizedTest
@CsvSource({
"'John Doe', 'San Francisco, CA', 94102",
"'Jane Smith', 'New York, NY', 10001"
})
void testQuotedValues(String name, String city, int zipCode) {
assertTrue(name.contains(" "));
assertTrue(city.contains(","));
assertTrue(zipCode > 0);
}
// Null and empty value handling
@ParameterizedTest
@CsvSource(value = {
"apple, , sweet", // Empty middle value
"banana, yellow, ", // Empty last value
"cherry, red, NIL" // Custom null value
}, nullValues = {"NIL"}, emptyValue = "EMPTY")
void testNullAndEmptyValues(String fruit, String color, String taste) {
assertNotNull(fruit);
// color and taste may be null or "EMPTY"
}
// Headers in display names
@ParameterizedTest(name = "Product: {arguments}")
@CsvSource(value = {
"ID, Name, Price",
"1, Apple, 1.50",
"2, Banana, 0.75"
}, useHeadersInDisplayName = true)
void testWithHeaders(String id, String name, String price) {
assertNotNull(id);
assertNotNull(name);
assertNotNull(price);
}
// Text block syntax (Java 15+)
@ParameterizedTest
@CsvSource(textBlock = """
1, apple, 1.50
2, banana, 0.75
3, cherry, 2.00
""")
void testTextBlock(int id, String fruit, double price) {
assertTrue(id > 0);
assertNotNull(fruit);
assertTrue(price > 0);
}
// Whitespace handling
@ParameterizedTest
@CsvSource(value = {
" apple , red ", // Whitespace will be trimmed
"' banana ', yellow" // Quoted whitespace preserved
}, ignoreLeadingAndTrailingWhitespace = true)
void testWhitespaceHandling(String fruit, String color) {
// apple will be trimmed, " banana " will preserve spaces
assertNotNull(fruit);
assertNotNull(color);
}
}Loads CSV data from classpath resources or file system files.
/**
* Loads CSV data from external files as arguments to parameterized tests
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@ArgumentsSource(CsvFileArgumentsProvider.class)
@Repeatable(CsvFileSources.class)
@interface CsvFileSource {
/**
* Classpath resources to load CSV data from
*/
String[] resources() default {};
/**
* File system paths to load CSV data from
*/
String[] files() default {};
/**
* Character encoding for file reading
*/
String encoding() default "UTF-8";
/**
* Line separator for parsing
*/
String lineSeparator() default "\n";
/**
* Number of lines to skip from beginning (e.g., headers)
*/
int numLinesToSkip() default 0;
// Inherits all parsing attributes from @CsvSource
boolean useHeadersInDisplayName() default false;
char quoteCharacter() default '"';
char delimiter() default ',';
String delimiterString() default "";
String emptyValue() default "";
String[] nullValues() default "";
int maxCharsPerColumn() default 4096;
boolean ignoreLeadingAndTrailingWhitespace() default true;
}Usage Examples:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
class CsvFileSourceExamples {
// Load from classpath resource
@ParameterizedTest
@CsvFileSource(resources = "/test-data/products.csv")
void testFromClasspathResource(int id, String name, double price) {
assertTrue(id > 0);
assertNotNull(name);
assertTrue(price >= 0);
}
// Load from file system
@ParameterizedTest
@CsvFileSource(files = "src/test/resources/users.csv")
void testFromFile(String username, String email, int age) {
assertNotNull(username);
assertTrue(email.contains("@"));
assertTrue(age >= 0);
}
// Skip header rows
@ParameterizedTest
@CsvFileSource(
resources = "/test-data/products-with-headers.csv",
numLinesToSkip = 1
)
void testSkipHeaders(int id, String name, double price) {
assertTrue(id > 0);
assertNotNull(name);
assertTrue(price >= 0);
}
// Custom encoding and delimiter
@ParameterizedTest
@CsvFileSource(
resources = "/test-data/international.csv",
encoding = "UTF-8",
delimiter = ';'
)
void testCustomEncodingAndDelimiter(String name, String country, String currency) {
assertNotNull(name);
assertNotNull(country);
assertNotNull(currency);
}
// Multiple files
@ParameterizedTest
@CsvFileSource(resources = {
"/test-data/products-q1.csv",
"/test-data/products-q2.csv"
})
void testMultipleFiles(int id, String name, double price) {
assertTrue(id > 0);
assertNotNull(name);
assertTrue(price >= 0);
}
// With custom null values
@ParameterizedTest
@CsvFileSource(
resources = "/test-data/incomplete-data.csv",
nullValues = {"N/A", "NULL", ""}
)
void testWithNullValues(String name, String value, String category) {
assertNotNull(name); // name should never be null
// value and category may be null based on nullValues
}
}Exception thrown when CSV parsing fails due to malformed data or configuration issues.
/**
* Exception thrown when CSV parsing fails
*/
@API(status = STABLE, since = "5.0")
class CsvParsingException extends JUnitException {
/**
* Constructs exception with message
*/
CsvParsingException(String message) { }
/**
* Constructs exception with message and cause
*/
CsvParsingException(String message, Throwable cause) { }
}Container annotations for multiple CSV source annotations.
/**
* Container annotation for multiple @CsvSource annotations
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@interface CsvSources {
CsvSource[] value();
}
/**
* Container annotation for multiple @CsvFileSource annotations
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
@interface CsvFileSources {
CsvFileSource[] value();
}Advanced CSV Features:
class AdvancedCsvExamples {
// Complex CSV with various data types
@ParameterizedTest
@CsvSource({
"1, 'Product A', 19.99, true, 2023-01-15",
"2, 'Product B', 29.50, false, 2023-02-20",
"3, 'Product \"Special\"', 39.99, true, 2023-03-10"
})
void testComplexCsv(int id, String name, double price, boolean available, String date) {
assertTrue(id > 0);
assertNotNull(name);
assertTrue(price > 0);
assertNotNull(date);
}
// Handling special characters and escaping
@ParameterizedTest
@CsvSource(value = {
"apple|'red, juicy'|1.50",
"banana|yellow|0.75",
"cherry|'dark red'|2.00"
}, delimiter = '|')
void testSpecialCharacters(String fruit, String description, double price) {
assertNotNull(fruit);
assertNotNull(description);
assertTrue(price > 0);
}
}The CSV sources provide powerful data-driven testing capabilities with fine-grained control over parsing behavior, making them ideal for complex test scenarios with structured data.
Install with Tessl CLI
npx tessl i tessl/maven-org-junit-jupiter--junit-jupiter-params