Appearance
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 type | For |
|---|---|
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:
| Placeholder | Meaning |
|---|---|
{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 madamjava
@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 = 3Without 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
- Which return types can a
@MethodSourcefactory use for one parameter, and which for several? - What happens when
@MethodSourcehas no method name? - How do you reference a factory method in another class?
- How does
@ArgumentsSourcediffer from@MethodSource? - What is one
Argumentsobject? - When would you use
ArgumentsAccessor? - What is the difference between
@ConvertWithand@AggregateWith? - Write a name pattern that produces
test 1: 1 + 2 = 3.
Answers
- One:
Stream<T>,Collection<T>,T[],Object[]. Several:Stream<Arguments>,Object[][]. - JUnit looks for a static method with the same name as the test.
@MethodSource("fully.qualified.ClassName#methodName").- The data comes from a class implementing
ArgumentsProvider, which always returns a stream ofArgumentsand receives theExtensionContext. - One set of parameters for one run.
- When a test would otherwise need many parameters; read them by index instead.
@ConvertWithconverts one raw value into one parameter;@AggregateWithcombines several values into one parameter (such as an object).@ParameterizedTest(name = "test {index}: {0} + {1} = {2}").