Developer-kit unit-test-parameterized
Provides parameterized testing patterns with JUnit 5, generates data-driven unit tests using @ParameterizedTest, @ValueSource, @CsvSource, @MethodSource. Creates tests that run the same logic with multiple input values. Use when writing data-driven Java tests, multiple test cases from single method, or boundary value analysis.
install
source · Clone the upstream repo
git clone https://github.com/giuseppe-trisciuoglio/developer-kit
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/giuseppe-trisciuoglio/developer-kit "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/developer-kit-java/skills/unit-test-parameterized" ~/.claude/skills/giuseppe-trisciuoglio-developer-kit-unit-test-parameterized && rm -rf "$T"
manifest:
plugins/developer-kit-java/skills/unit-test-parameterized/SKILL.mdsource content
Parameterized Unit Tests with JUnit 5
Overview
Provides patterns for parameterized unit tests in Java using JUnit 5. Covers
@ValueSource, @CsvSource, @MethodSource, @EnumSource, @ArgumentsSource, and custom display names. Reduces test duplication by running the same test logic with multiple input values.
When to Use
- Writing JUnit tests with multiple input combinations
- Implementing data-driven tests in Java
- Running same test with different values (boundary analysis)
- Testing multiple scenarios from single test method
Instructions
- Add dependency: Ensure
is on test classpath (included injunit-jupiter-params
)junit-jupiter - Choose source:
for simple values,@ValueSource
for tabular data,@CsvSource
for complex objects@MethodSource - Match parameters: Test method parameters must match data source types
- Set display names: Use
for readable outputname = "{0}..." - Validate: Run
or./gradlew test --info
and verify all parameter combinations executemvn test
Examples
Maven / Gradle Dependency
JUnit 5 parameterized tests require
junit-jupiter (includes params). Add assertj-core for assertions:
<!-- Maven --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency>
// Gradle testImplementation("org.junit.jupiter:junit-jupiter")
@ValueSource
— Simple Values
@ValueSourceimport org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.Assertions.*; @ParameterizedTest @ValueSource(strings = {"hello", "world", "test"}) void shouldCapitalizeAllStrings(String input) { assertThat(StringUtils.capitalize(input)).isNotEmpty(); } @ParameterizedTest @ValueSource(ints = {1, 2, 3, 4, 5}) void shouldBePositive(int number) { assertThat(number).isPositive(); } @ParameterizedTest @ValueSource(ints = {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}) void shouldHandleBoundaryValues(int value) { assertThat(Math.incrementExact(value)).isGreaterThan(value); }
@CsvSource
— Tabular Data
@CsvSourceimport org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @ParameterizedTest @CsvSource({ "alice@example.com, true", "bob@gmail.com, true", "invalid-email, false", "user@, false", "@example.com, false" }) void shouldValidateEmailAddresses(String email, boolean expected) { assertThat(UserValidator.isValidEmail(email)).isEqualTo(expected); }
@MethodSource
— Complex Data
@MethodSourceimport org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import java.util.stream.Stream; @ParameterizedTest @MethodSource("additionTestCases") void shouldAddNumbersCorrectly(int a, int b, int expected) { assertThat(Calculator.add(a, b)).isEqualTo(expected); } static Stream<Arguments> additionTestCases() { return Stream.of( Arguments.of(1, 2, 3), Arguments.of(0, 0, 0), Arguments.of(-1, 1, 0), Arguments.of(100, 200, 300) ); }
@EnumSource
— Enum Values
@EnumSource@ParameterizedTest @EnumSource(Status.class) void shouldHandleAllStatuses(Status status) { assertThat(status).isNotNull(); } @ParameterizedTest @EnumSource(value = Status.class, names = {"ACTIVE", "INACTIVE"}) void shouldHandleSpecificStatuses(Status status) { assertThat(status).isIn(Status.ACTIVE, Status.INACTIVE); }
Custom Display Names
@ParameterizedTest(name = "Discount of {0}% should be calculated correctly") @ValueSource(ints = {5, 10, 15, 20}) void shouldApplyDiscount(int discountPercent) { double result = DiscountCalculator.apply(100.0, discountPercent); assertThat(result).isEqualTo(100.0 * (1 - discountPercent / 100.0)); }
Custom ArgumentsProvider
ArgumentsProviderclass RangeValidatorProvider implements ArgumentsProvider { @Override public Stream<? extends Arguments> provideArguments(ExtensionContext context) { return Stream.of( Arguments.of(0, 0, 100, true), Arguments.of(50, 0, 100, true), Arguments.of(-1, 0, 100, false), Arguments.of(101, 0, 100, false) ); } } @ParameterizedTest @ArgumentsSource(RangeValidatorProvider.class) void shouldValidateRange(int value, int min, int max, boolean expected) { assertThat(RangeValidator.isInRange(value, min, max)).isEqualTo(expected); }
Error Condition Testing
@ParameterizedTest @ValueSource(strings = {"", " ", null}) void shouldThrowExceptionForInvalidInput(String input) { assertThatThrownBy(() -> Parser.parse(input)) .isInstanceOf(IllegalArgumentException.class); }
Best Practices
- Use descriptive display names:
for readable outputname = "{0}..." - Test boundary values: include min, max, zero, and edge cases
- Keep test logic focused: single assertion per parameter set
- Use
for complex objects,@MethodSource
for tabular data@CsvSource - Organize test data logically — group related scenarios together
Constraints and Warnings
- Parameter count must match: Number of parameters from source must match test method signature
limitation: Only supports primitives, strings, and enums — not objects or null directly@ValueSource- CSV escaping: Strings with commas must use single quotes in
@CsvSource
visibility: Factory methods must be static in the same test class@MethodSource- Display name placeholders: Use
,{0}
, etc. to reference parameters{1} - Execution count: Each parameter set runs as a separate test invocation