JUnit Jupiter Parameterized Test Extension
This project extends JUnit Jupiter parameterized tests with generated parameter values for numbers, date/time types, strings, booleans, and enums.
It is designed to reduce @MethodSource boilerplate while preserving type safety and adding compile-time validation for supported annotations.
Installation
Runtime Extension
Gradle:
dependencies {
testImplementation("com.wesleyhome.test:junit-jupiter-params-generated:<latestVersion>")
}
Maven:
<dependency>
<groupId>com.wesleyhome.test</groupId>
<artifactId>junit-jupiter-params-generated</artifactId>
<version>${latestVersion}</version>
<scope>test</scope>
</dependency>
Annotation Processor (Optional, recommended)
Gradle (KSP):
plugins {
id("com.google.devtools.ksp") version "<kspVersion>"
}
dependencies {
ksp("com.wesleyhome.test:annotation-processor:<latestVersion>")
}
Maven:
<dependency>
<groupId>com.wesleyhome.test</groupId>
<artifactId>annotation-processor</artifactId>
<version>${latestVersion}</version>
<scope>provided</scope>
</dependency>
Compatibility Matrix
| Component | Supported |
|---|---|
| Java | 17+ |
| Kotlin/JVM | 2.3.x |
| JUnit Jupiter | 6.0.3+ |
| Gradle | Supported (testImplementation, optional ksp) |
| Maven | Supported (test scope for extension) |
Getting Started
import com.wesleyhome.test.jupiter.annotations.GeneratedParametersTest
import com.wesleyhome.test.jupiter.annotations.number.IntRangeSource
import kotlin.test.assertTrue
@GeneratedParametersTest
fun testWithGeneratedParameters(@IntRangeSource(min = 1, max = 10) value: Int) {
assertTrue(value in 1..10)
}
Core Sources
-
Numeric ranges:
@IntRangeSource,@LongRangeSource,@DoubleRangeSource,@FloatRangeSource -
Numeric explicit values:
@IntSource,@LongSource,@DoubleSource,@FloatSource -
Date/time ranges:
@InstantRangeSource,@LocalDateRangeSource,@LocalDateTimeRangeSource,@LocalTimeRangeSource -
Date/time explicit values:
@InstantSource,@LocalDateSource,@LocalDateTimeSource,@LocalTimeSource -
Random date/time:
@RandomInstantSource -
Other types:
@StringSource, enums, booleans
Parameter Combinations
When multiple parameters are generated, the extension uses a Cartesian product.
Invocation count:
totalInvocations = product(parameterOptionCounts)
Example:
@GeneratedParametersTest
fun combinations(
@IntRangeSource(min = 1, max = 3) first: Int, // 3 values
@IntRangeSource(min = 10, max = 12) second: Int // 3 values
) {
// Runs 9 times (3 * 3)
}
CI warning: two larger ranges can multiply quickly (for example, 100 x 100 = 10,000 invocations).
Generated vs Random Values
-
Range and explicit-value sources are deterministic by definition.
-
@RandomInstantSourceis deterministic by default in this library (fixed internal seed). -
Invocation display names include resolved argument values, so failures show the generated value directly.
Because random generation is deterministic by default, rerunning the same test configuration reproduces the same sequence.
Performance and Memory
Generation model:
-
Parameter value lists are generated eagerly per parameter.
-
Test invocations are iterated lazily across the Cartesian product.
Practical guidance:
-
Keep ranges intentional.
-
Prefer smaller random
sizevalues. -
Split high-cardinality tests into targeted suites.
Why Not @MethodSource?
| Concern | @MethodSource |
Generated Sources |
|---|---|---|
| Boilerplate | Requires separate provider methods | Inlined annotation configuration |
| Type safety | Depends on method return wiring | Annotation-driven parameter typing |
| Range/date convenience | Manual list/stream construction | Built-in numeric and date/time sources |
| Compile-time validation | Limited by default | Supported via optional annotation processor |
@CsvSource vs Generated Parameters
@CsvSource for a combinatorial check requires manually listing every pair:
@ParameterizedTest
@CsvSource({
"1,10",
"1,20",
"1,30",
"2,10",
"2,20",
"2,30",
"3,10",
"3,20",
"3,30"
})
void multipliesAllPairs(int left, int right) {
assertTrue((left * right) > 0);
}
Equivalent generated-parameter test (same 3 x 3 Cartesian product, no manual tuple list):
@GeneratedParametersTest
void multipliesAllPairs(
@IntRangeSource(min = 1, max = 3) int left,
@IntRangeSource(min = 10, max = 30, increment = 10) int right
) {
assertTrue((left * right) > 0);
}
Annotation Processor
The optional annotation processor validates supported annotation configurations at compile time.
Example (compile-time error when min > max):
@GeneratedParametersTest
void invalid(@IntRangeSource(min = 10, max = 1) int value) {
}
Data Flow
[Test Method] -> [Parameter Scan] -> [Per-Parameter Data Generation] -> [Invocation Iteration] -> [Test Execution]
Custom Annotations and Providers
You can create custom providers by:
-
Defining a parameter annotation.
-
Mapping it with
@SourceProvider(...). -
Implementing
ParameterDataProvider<T>.
Example:
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@SourceProvider(CustomSourceDataProvider::class)
annotation class CustomSource(val min: Int, val max: Int, val increment: Int)
class CustomSourceDataProvider : ParameterDataProvider<Int> {
override fun createParameterOptionsData(testParameter: TestParameter): List<Int> {
val annotation = testParameter.annotations.filterIsInstance<CustomSource>().first()
return (annotation.min..annotation.max step annotation.increment).toList()
}
}
Testing
Run all tests:
./gradlew test