Back to Testing & Quality Assurance

unit-test-parameterized

JUnit 5unit testingparameterized testsdata-driven testingtest automationcode qualityJavasoftware testing
282📄 MIT🕒 2026-06-15Source ↗

Install this skill

npx skills add giuseppe-trisciuoglio/developer-kit

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

Parameterized unit tests transform standard test methods into iterative loops by injecting distinct input variables into a shared verification logic. By moving away from writing separate methods for every edge case, this approach keeps test suites concise and maintainable. JUnit 5 provides multiple providers to supply these values, allowing developers to map scalar primitives, enum constants, tabular CSV rows, or dynamic stream objects directly into test parameters. This technique significantly boosts code coverage because adding new test vectors becomes a matter of updating a data source rather than duplicating entire test structures. The integration with AssertJ provides expressive verification syntax, ensuring that each iteration remains readable and precise. Ultimately, this practice enforces high-quality standard testing through data-driven automation, reducing redundant boilerplate code while ensuring logic holds up across broad input variations.

When to Use This Skill

  • Verifying mathematical functions with extensive input ranges
  • Checking regex-based validation for diverse string formats
  • Testing state transition logic against multiple enum states
  • Validating boundary conditions for API request processing
  • Importing large sets of real-world sample data for regression testing

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • Refactor these redundant tests into a parameterized JUnit 5 test
  • How do I run the same test with different inputs in Java?
  • Convert this test method to use @ParameterizedTest
  • Show me how to pass CSV data into my unit tests
  • How to test enum values efficiently in JUnit 5?

Pro Tips

  • 💡Combine `@ParameterizedTest` with custom `ArgumentsProvider` for complex data structures or external data sources, enhancing flexibility beyond simple `ValueSource` or `CsvSource`.
  • 💡Always provide a descriptive name for your parameterized tests using the `name` attribute (e.g., `{index}: {0} should be {1}`) to make test reports more readable and actionable.
  • 💡When testing a large number of scenarios, consider grouping related test data into separate parameterized test methods to maintain clarity and focus for each specific behavior.

What this skill does

  • Execute identical test logic across multiple input variations
  • Streamline data-driven scenarios using tabular formats
  • Filter enum values for partial or specific test iterations
  • Read external CSV datasets for large-scale integration checks
  • Provide custom display naming for individual test cycles

When not to use it

  • When test methods have drastically different assertions or behavior
  • For simple tests that only require a single, trivial input
  • When the setup logic for each test case is unique and complex

Example workflow

  1. Identify a test method with significant code duplication across input variants
  2. Select the appropriate provider, such as @ValueSource or @CsvSource, based on data shape
  3. Replace the hardcoded test variables with method arguments
  4. Annotate the method with @ParameterizedTest and the chosen source
  5. Run the test suite to observe individual results for each provided data point

Prerequisites

  • JUnit 5 (Jupiter) library
  • AssertJ for fluent assertions
  • A standard Java testing project structure

Pitfalls & limitations

  • !Mixing incompatible data types in sources can lead to runtime conversion errors
  • !Over-parameterizing simple tests can obscure the intent of the failure
  • !Relying on external CSV files can break tests if file paths or formatting change
  • !Complex MethodSource implementations can become difficult to debug

FAQ

How do I label individual test iterations in the IDE?
Use the name attribute in the @ParameterizedTest annotation to define custom display names, including placeholders like {index} or {arguments}.
Can I mix different data sources in a single test?
No, a single test method can generally use only one provider annotation. You can use @ArgumentsSource if you require more complex, custom, or combined data logic.
Are parameterized tests slower than standard tests?
The execution speed is equivalent to running multiple standard tests, but the setup cost is lower because the infrastructure is initialized once for the provider.
What happens if one parameter iteration fails?
JUnit treats each set of parameters as an individual test execution; a single failure will not stop the remaining inputs from being tested.

How it compares

Unlike manual test duplication where every scenario requires a new method block, parameterized testing centralizes the verification logic, making it easier to maintain and update the entire test set from a single location.

Source & trust

282 stars📄 MIT🕒 Updated 2026-06-15
📄 Full skill instructions — original source: giuseppe-trisciuoglio/developer-kit
# Parameterized Unit Tests with JUnit 5

Write efficient parameterized unit tests that run the same test logic with multiple input values. Reduce test duplication and improve test coverage using @ParameterizedTest.

## When to Use This Skill

Use this skill when:
- Testing methods with multiple valid inputs
- Testing boundary values systematically
- Testing multiple invalid inputs for error cases
- Want to reduce test duplication
- Testing multiple scenarios with similar assertions
- Need data-driven testing approach

## Setup: Parameterized Testing

### Maven
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>


### Gradle
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.assertj:assertj-core")
}


## Basic Pattern: @ValueSource

### Simple Value Testing

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.*;

class StringUtilsTest {

@ParameterizedTest
@ValueSource(strings = {"hello", "world", "test"})
void shouldCapitalizeAllStrings(String input) {
String result = StringUtils.capitalize(input);
assertThat(result).startsWith(input.substring(0, 1).toUpperCase());
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void shouldBePositive(int number) {
assertThat(number).isPositive();
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
void shouldHandleBothBooleanValues(boolean value) {
assertThat(value).isNotNull();
}
}


## @MethodSource for Complex Data

### Factory Method Data Source

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

class CalculatorTest {

static Stream<org.junit.jupiter.params.provider.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),
Arguments.of(-5, -10, -15)
);
}

@ParameterizedTest
@MethodSource("additionTestCases")
void shouldAddNumbersCorrectly(int a, int b, int expected) {
int result = Calculator.add(a, b);
assertThat(result).isEqualTo(expected);
}
}


## @CsvSource for Tabular Data

### CSV-Based Test Data

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class UserValidationTest {

@ParameterizedTest
@CsvSource({
"[email protected], true",
"[email protected], true",
"invalid-email, false",
"user@, false",
"@example.com, false",
"user [email protected], false"
})
void shouldValidateEmailAddresses(String email, boolean expected) {
boolean result = UserValidator.isValidEmail(email);
assertThat(result).isEqualTo(expected);
}

@ParameterizedTest
@CsvSource({
"123-456-7890, true",
"555-123-4567, true",
"1234567890, false",
"123-45-6789, false",
"abc-def-ghij, false"
})
void shouldValidatePhoneNumbers(String phone, boolean expected) {
boolean result = PhoneValidator.isValid(phone);
assertThat(result).isEqualTo(expected);
}
}


## @CsvFileSource for External Data

### CSV File-Based Testing

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;

class PriceCalculationTest {

@ParameterizedTest
@CsvFileSource(resources = "/test-data/prices.csv", numLinesToSkip = 1)
void shouldCalculateTotalPrice(String product, double price, int quantity, double expected) {
double total = PriceCalculator.calculateTotal(price, quantity);
assertThat(total).isEqualTo(expected);
}
}

// test-data/prices.csv:
// product,price,quantity,expected
// Laptop,999.99,1,999.99
// Mouse,29.99,3,89.97
// Keyboard,79.99,2,159.98


## @EnumSource for Enum Testing

### Enum-Based Test Data

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

enum Status { ACTIVE, INACTIVE, PENDING, DELETED }

class StatusHandlerTest {

@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);
}

@ParameterizedTest
@EnumSource(value = Status.class, mode = EnumSource.Mode.EXCLUDE, names = {"DELETED"})
void shouldHandleStatusesExcludingDeleted(Status status) {
assertThat(status).isNotEqualTo(Status.DELETED);
}
}


## Custom Display Names

### Readable Test Output

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class DiscountCalculationTest {

@ParameterizedTest(name = "Discount of {0}% should be calculated correctly")
@ValueSource(ints = {5, 10, 15, 20})
void shouldApplyDiscount(int discountPercent) {
double originalPrice = 100.0;
double discounted = DiscountCalculator.apply(originalPrice, discountPercent);
double expected = originalPrice * (1 - discountPercent / 100.0);

assertThat(discounted).isEqualTo(expected);
}

@ParameterizedTest(name = "User role {0} should have {1} permissions")
@CsvSource({
"ADMIN, 100",
"MANAGER, 50",
"USER, 10"
})
void shouldHaveCorrectPermissions(String role, int expectedPermissions) {
User user = new User(role);
assertThat(user.getPermissionCount()).isEqualTo(expectedPermissions);
}
}


## Combining Multiple Sources

### ArgumentsProvider for Complex Scenarios

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import java.util.stream.Stream;

class RangeValidatorArgumentProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of(0, 0, 100, true), // Min boundary
Arguments.of(100, 0, 100, true), // Max boundary
Arguments.of(50, 0, 100, true), // Middle value
Arguments.of(-1, 0, 100, false), // Below range
Arguments.of(101, 0, 100, false) // Above range
);
}
}

class RangeValidatorTest {

@ParameterizedTest
@ArgumentsSource(RangeValidatorArgumentProvider.class)
void shouldValidateRangeCorrectly(int value, int min, int max, boolean expected) {
boolean result = RangeValidator.isInRange(value, min, max);
assertThat(result).isEqualTo(expected);
}
}


## Testing Edge Cases with Parameters

### Boundary Value Analysis

class BoundaryValueTest {

@ParameterizedTest
@ValueSource(ints = {
Integer.MIN_VALUE, // Absolute minimum
Integer.MIN_VALUE + 1, // Just above minimum
-1, // Negative boundary
0, // Zero boundary
1, // Just above zero
Integer.MAX_VALUE - 1, // Just below maximum
Integer.MAX_VALUE // Absolute maximum
})
void shouldHandleAllBoundaryValues(int value) {
int incremented = MathUtils.increment(value);
assertThat(incremented).isNotLessThan(value);
}

@ParameterizedTest
@CsvSource({
", false", // null
"'', false", // empty
"' ', false", // whitespace only
"a, true", // single character
"abc, true" // normal
})
void shouldValidateStrings(String input, boolean expected) {
boolean result = StringValidator.isValid(input);
assertThat(result).isEqualTo(expected);
}
}


## Repeat Tests

### Run Same Test Multiple Times

import org.junit.jupiter.api.RepeatedTest;

class ConcurrencyTest {

@RepeatedTest(100)
void shouldHandleConcurrentAccess() {
// Test that might reveal race conditions if run multiple times
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
assertThat(counter.get()).isEqualTo(1);
}
}


## Best Practices

- **Use @ParameterizedTest** to reduce test duplication
- **Use descriptive display names** with (name = "...")
- **Test boundary values** systematically
- **Keep test logic simple** - focus on single assertion
- **Organize test data logically** - group similar scenarios
- **Use @MethodSource** for complex test data
- **Use @CsvSource** for tabular test data
- **Document expected behavior** in test names

## Common Patterns

**Testing error conditions**:
@ParameterizedTest
@ValueSource(strings = {"", " ", null})
void shouldThrowExceptionForInvalidInput(String input) {
assertThatThrownBy(() -> Parser.parse(input))
.isInstanceOf(IllegalArgumentException.class);
}


**Testing multiple valid inputs**:
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 5, 8, 13})
void shouldBeInFibonacciSequence(int number) {
assertThat(FibonacciChecker.isFibonacci(number)).isTrue();
}


## Troubleshooting

**Parameter not matching**: Verify number and type of parameters match test method signature.

**Display name not showing**: Check parameter syntax in name = "...".

**CSV parsing error**: Ensure CSV format is correct and quote strings containing commas.

## References

- [JUnit 5 Parameterized Tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests)
- [@ParameterizedTest Documentation](https://junit.org/junit5/docs/current/api/org.junit.jupiter.params/org/junit/jupiter/params/ParameterizedTest.html)
- [Boundary Value Analysis](https://en.wikipedia.org/wiki/Boundary-value_analysis)

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/unit-test-parameterized/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/giuseppe-trisciuoglio/developer-kit/unit-test-parameterized/SKILL.md
  • Cursor: ~/.cursor/skills/giuseppe-trisciuoglio/developer-kit/unit-test-parameterized/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/giuseppe-trisciuoglio/developer-kit/unit-test-parameterized/SKILL.md

🚀 Install with CLI:
npx skills add giuseppe-trisciuoglio/developer-kit

Read the Master Guide: Mastering Agent Skills

Related Skill Units

Recommended Rules

View more rules

Recommended Workflows

View more workflows

Recommended MCP Servers

View more MCP servers

Take It Further

Maximize your productivity with these powerful resources

📋

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
📖

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid testing & quality assurance issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under Testing & Quality Assurance and is published by Giuseppe Trisciuoglio, maintained in giuseppe-trisciuoglio/developer-kit.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.