Skip to content
Chapter 09 · JUnit 5 & Mockito

Parameterized Tests Part 2: Objects and Complex Data ​

Part 1 fed single parameters (@ValueSource, @EnumSource) and multiple parameters (@CsvSource, @CsvFileSource). This part covers objects and more complex data.

@MethodSource ​

Provides the test data from a static method (a "factory method"), in the same class or another one.

java
@ParameterizedTest
@MethodSource("palindromeProvider")
void testPalindrome(String word) {
    assertTrue(MyStringUtil.isPalindrome(word));
}

static Stream<String> palindromeProvider() {
    return Stream.of("civic", "radar");   // run 1: "civic", run 2: "radar"
}

The factory method's return type decides how the data is fed:

Return typeFor
Stream<T>one parameter; each element is one run
Collection<T> (e.g. List<T>)one parameter
T[] or Object[]one parameter
Stream<Arguments>several parameters; each Arguments is one run
Object[][]several parameters; each inner array is one run

T can be anything: a String, an Integer, your own User.

Without a method name, JUnit looks for a static method with the same name as the test:

java
@ParameterizedTest
@MethodSource
void testPalindromeList(String word) { ... }

static List<String> testPalindromeList() {
    return List.of("civic", "level", "madam");
}

Objects:

java
@ParameterizedTest
@MethodSource("userProvider")
void testUser(User user) { ... }

static Object[] userProvider() {
    return new Object[] { new User("A", 1), new User("B", 2) };   // two runs
}

Several parameters, with Arguments.of(...):

java
@ParameterizedTest
@MethodSource("wordLengthProvider")
void testLength(String word, int length) {
    assertEquals(length, word.length());
}

static Stream<Arguments> wordLengthProvider() {
    return Stream.of(
            Arguments.of("hello", 5),   // run 1
            Arguments.of("world", 5)    // run 2
    );
}

Object[][] works the same way: new Object[][] { {1, 2, 3}, {5, 5, 10} } gives two runs of three parameters.

A factory method in another class: use its fully qualified name and #:

java
@MethodSource("com.example.params.UserData#users")

@ArgumentsSource ​

Like @MethodSource, but the data comes from a separate class implementing ArgumentsProvider. Good for complex test data.

java
@ParameterizedTest
@ArgumentsSource(UserArgumentsProvider.class)
void testUser(User user) { ... }
java
public class UserArgumentsProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of(
                Arguments.of(new User("C", 3)),   // run 1
                Arguments.of(new User("D", 4))    // run 2
        );
    }
}

An Arguments is one set of parameters for one run: Arguments.of(1, 2, 3) (several parameters), Arguments.of("Hello") (one), Arguments.of(userObj) (an object).

Differences from @MethodSource:

  • One return type only: a stream of Arguments, for one or several parameters.
  • It receives the ExtensionContext (see the extensions chapter), with everything about the test: its method, class, instance and annotations, should the data depend on them. (Recent JUnit versions add an overload that also receives the test's parameter declarations.)

ArgumentsAccessor ​

A test with many parameters gets an unwieldy signature:

java
void testWithManyParameters(String arg1, String arg2, String arg3, int arg4, int arg5, int arg6 ...)

Instead, take a single ArgumentsAccessor and read the values by index:

java
@ParameterizedTest
@CsvSource({"A, 25", "B, 30"})
void testUser(ArgumentsAccessor accessor) {
    User user = new User(accessor.getString(0), accessor.getInteger(1));
    ...
}

@AggregateWith ​

Turns several values into one object automatically. Part 1's @ConvertWith converts one value into one parameter; @AggregateWith combines several values into one parameter.

java
@ParameterizedTest
@CsvSource({"A, 25", "B, 30"})
void testUser(@AggregateWith(UserAggregator.class) User user) {
    ...
}
java
public class UserAggregator implements ArgumentsAggregator {

    @Override
    public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) {
        return new User(accessor.getString(0), accessor.getInteger(1));
    }
}

The steps mirror @ConvertWith: the source supplies the raw values ("A", "25"); the aggregator builds the User; the User is injected; the test runs.

Display names ​

@ParameterizedTest's name controls the name of each run:

PlaceholderMeaning
{index}the current run number
{arguments}all arguments, comma separated
{0}, {1}, {2}, ...individual arguments
java
@ParameterizedTest(name = "test {index}: testing palindrome for {0}")
@ValueSource(strings = {"civic", "madam"})
void testPalindrome(String word) { ... }
// test 1: testing palindrome for civic
// test 2: testing palindrome for madam
java
@ParameterizedTest(name = "test {index}: {arguments}")
@CsvSource({"1, 2, 3", "5, 5, 10"})
void testAdd(int a, int b, int expected) { ... }
// test 1: 1, 2, 3
// test 2: 5, 5, 10

@ParameterizedTest(name = "test {index}: {0} + {1} = {2}")
@CsvSource({"1, 2, 3"})
void testAdd2(int a, int b, int expected) { ... }
// test 1: 1 + 2 = 3

Without a name, runs are named like [1] 1, 2, 3.

Next: So far every test has always run. Chapter 10 covers assumptions: conditions that decide, inside a test, whether it should run at all.

Check yourself ​

  1. Which return types can a @MethodSource factory use for one parameter, and which for several?
  2. What happens when @MethodSource has no method name?
  3. How do you reference a factory method in another class?
  4. How does @ArgumentsSource differ from @MethodSource?
  5. What is one Arguments object?
  6. When would you use ArgumentsAccessor?
  7. What is the difference between @ConvertWith and @AggregateWith?
  8. Write a name pattern that produces test 1: 1 + 2 = 3.
Answers
  1. One: Stream<T>, Collection<T>, T[], Object[]. Several: Stream<Arguments>, Object[][].
  2. JUnit looks for a static method with the same name as the test.
  3. @MethodSource("fully.qualified.ClassName#methodName").
  4. The data comes from a class implementing ArgumentsProvider, which always returns a stream of Arguments and receives the ExtensionContext.
  5. One set of parameters for one run.
  6. When a test would otherwise need many parameters; read them by index instead.
  7. @ConvertWith converts one raw value into one parameter; @AggregateWith combines several values into one parameter (such as an object).
  8. @ParameterizedTest(name = "test {index}: {0} + {1} = {2}").