Parameterized Test in JUnit 5

If you wrote unit tests with JUnit 4, a notable pain point was the lack of first-class parameter support in test methods, leading to extensive boilerplate duplication when validating multiple inputs :-1:. While TestNG addressed this earlier, JUnit 5 introduced native Parameterized Tests to solve this elegantly :heart:.

JUnit5-logo

Required Setup

To use Parameterized Tests, add the junit-jupiter-params dependency.

With Gradle:

1
testImplementation('org.junit.jupiter:junit-jupiter-params:5.7.1')

With Maven:

1
2
3
4
5
6
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.7.1</version>
    <scope>test</scope>
</dependency>

Argument Sources

Arguments for Parameterized Tests can be provided using various Argument Sources:

@ValueSource

The simplest way to supply literal test inputs. @ValueSource supports the following literal types:

  • short
  • byte
  • int
  • long
  • float
  • double
  • char
  • boolean
  • java.lang.String
  • java.lang.Class

The following example tests String.isNullOrBlank against 6 test inputs listed in @ValueSource:

1
2
3
4
5
6
7
  @ParameterizedTest(name = "#{index} - String.isNullOrBlank() of [{0}]")
  @EmptySource
  @NullSource
  @ValueSource(strings = ["", " ", "  ", "        ", "\n", "\t"])
  fun `test isNullOrBlank`(str: String?) {
    assertTrue(str.isNullOrBlank())
  }

To test null and empty strings explicitly, you can also use @NullSource and @EmptySource (or the combined @NullAndEmptySource).

@EnumSource

Supplies constants from an enum. By default, it passes all enum constants. To test a specific subset, specify the constant names as shown below.

For example, verifying that April, June, September, and November each have 30 days:

1
2
3
4
5
6
  @ParameterizedTest(name = "#{index} - [{0}] have 30 day")
  @EnumSource(value = Month::class, names = ["APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"])
  fun `test month have 30 day`(month: Month) {
    val isALeapYear = false
    assertEquals(30, month.length(isALeapYear))
  }

@MethodSource

While @ValueSource and @EnumSource work well for simple literals, complex test objects require a factory method. Note that the factory method must be @JvmStatic in Kotlin / static in Java.

Here is an optimized version of the test from Use Mock to make Unit Test easy consolidated into a single parameterized test method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
  @ParameterizedTest(name = "#{0} - {1} -> {2}")
  @MethodSource("provideToTestData")
  fun `test getFullNameCustomerWithTotalBuyPriceGreaterThan`(
    description: String,
    expected: List<String>,
    mockReturn: List<Customer>
  ) {
    val totalBuyPrice = 3_000

    every {
      customerRepository.findByTotalBuyPriceGreaterThan(3_000)
    } returns mockReturn

    assertEquals(
      expected,
      customerService.getFullNameCustomerWithTotalBuyPriceGreaterThan(3_000)
    )

    verify(exactly = 1) { customerRepository.findByTotalBuyPriceGreaterThan(totalBuyPrice) }

  }

with the data supplied by:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  companion object {
    @JvmStatic
    fun provideToTestData() =
      Stream.of(
        Arguments.of(
          "Empty list",
          emptyList<String>(),
          emptyList<Customer>()
        ),

        Arguments.of(
          "One customer",
          listOf("Tri Le"),
          listOf(
            Customer(firstName = "Tri", lastName = "Le")
          )
        ),

        Arguments.of(
          "Two customers",
          listOf("Tri Le", "Anna Wesley"),
          listOf(
            Customer(firstName = "Tri", lastName = "Le"),
            Customer(firstName = "Anna", lastName = "Wesley"),
          )
        )
      )
  }

Note: The factory method typically returns a Stream of Arguments. However, JUnit 5 also supports:

  • DoubleStream, LongStream, IntStream
  • Collection
  • Iterator, Iterable
  • Array of objects or primitives

@CsvSource

Supplies tabular test arguments as comma-separated values.

Testing String.toUpperCase:

1
2
3
4
5
6
  @ParameterizedTest(name = "#{index} - String.toUpperCase() of [{0}]")
  @CsvSource("trile,TRILE", "tRilE,TRILE", "trILE,TRILE")
  fun `test trim`(str: String, expected: String) {
    val actual = str.toUpperCase()
    assertEquals(expected, actual)
  }

@CsvFileSource

Similar to @CsvSource, but loads test data directly from an external CSV resource file.

1
2
3
4
5
6
  @ParameterizedTest(name = "#{index} - String.toUpperCase() of [{0}]")
  @CsvFileSource(resources = ["/trim.csv"], numLinesToSkip = 1)
  fun `test trim csv file`(str: String, expected: String) {
    val actual = str.toUpperCase()
    assertEquals(expected, actual)
  }

with trim.csv:

str,expected
trile,TRILE
tRilE,TRILE
trILE,TRILE

@ArgumentsSource

Provides test arguments via a custom implementation class of ArgumentsProvider rather than an inline factory method:

1
2
3
4
5
  @ParameterizedTest
  @ArgumentsSource(MyArgumentsProvider::class)
  fun testWithArgumentsSource(argument: String?) {
    assertNotNull(argument)
  }
1
2
3
4
5
6
7
8
9
class MyArgumentsProvider : ArgumentsProvider {
  override fun provideArguments(context: ExtensionContext): Stream<out Arguments> {
    return Stream.of("apple", "banana").map { arguments: String? ->
      Arguments.of(
        arguments
      )
    }
  }
}

Customizing Display Names

By default, parameterized test reports can sometimes be difficult to read in CI dashboards:

1
2
3
4
5
6
  @ParameterizedTest
  @EnumSource(value = Month::class, names = ["APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"])
  fun `test month have 30 day`(month: Month) {
    val isALeapYear = false
    assertEquals(30, month.length(isALeapYear))
  }

Yields default names:

TestMethod nameDurationResult
[1] month=APRILtest month have 30 day(Month)[1]0.033spassed
[2] month=JUNEtest month have 30 day(Month)[2]0.001spassed
[3] month=SEPTEMBERtest month have 30 day(Month)[3]0.001spassed
[4] month=NOVEMBERtest month have 30 day(Month)[4]0.001spassed

You can customize the display template using the name attribute:

1
2
3
4
5
6
  @ParameterizedTest(name = "#{index} - [{0}] have 30 day")
  @EnumSource(value = Month::class, names = ["APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"])
  fun `test month have 30 day`(month: Month) {
    val isALeapYear = false
    assertEquals(30, month.length(isALeapYear))
  }

Now the test reports display cleanly:

TestMethod nameDurationResult
#1 - [APRIL] have 30 daytest month have 30 day(Month)[1]0.034spassed
#2 - [JUNE] have 30 daytest month have 30 day(Month)[2]0.001spassed
#3 - [SEPTEMBER] have 30 daytest month have 30 day(Month)[3]0.001spassed
#4 - [NOVEMBER] have 30 daytest month have 30 day(Month)[4]0.001spassed

Placeholders available in name:

  • {index}: The current invocation index (1-based).
  • {arguments}: The complete list of arguments.
  • {0}, {1}, …: Individual argument values by 0-based index.
Reference articles
updatedupdated2026-09-052026-09-05
Load Comments?