Home Junit 5 Junit 5 Parameterized Tests with examples

Junit 5 Parameterized Tests with examples

- Advertisement -

In this article we will see what are the Junit 5 Parameterized tests, how to create parameterized test cases, several kind of argument sources for parameterized tests supported by Junit 5, argument conversion and arguments aggregators with several examples.

Content :

  1. JUnit 5 Parameterized Test
    1. Writing Parametrerized Test
    2. Customizing Display Names in Parameterized tests
  2. Argument sources for parameterized tests
    1. @ValueSource
    2. @EnumSource
    3. @MethodSource
    4. @CsvSource
    5. @CsvFileSource
    6. @ArgumentSource Custom Arguments Provider
  3. Argument conversion
    1. Implicit Conversion
    2. Explicit Conversion
      1. Argument Converter
      2. Argument converter using @ConvertWith
  4. Argument Aggregation for Junit 5 Parameterized tests
    1. ArgumentsAccessor
    2. Custom Argument Aggregators

1. Junit 5 Parameterized Test

Some times we may need to run same tests with different arguments or values, Junit 5 Parameterized tests make it possible to run a test multiple times with different arguments. They are declared just like regular @Test methods but use the @ParameterizedTest annotation instead of @Test annotation.

1.1. Writing parameterized tests

Following are the steps to create parameterized tests in Junit 5.

  1. Declare @ParameterizedTest to the test.
  2. Declare at least one source (example – @ValueSource) that will provide the arguments for each invocation of test.
  3. Consume the arguments in the test method .

JUnit 5 parameterized test maven dependency : We need junit-jupiter-params maven dependencies to support parameterized tests.

<!-- Jupiter API for writing tests -->
	<dependencies>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>${junit.jupiter.version}</version>
			<scope>test</scope>
		</dependency>
		<!-- Parameterized Tests -->
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-params</artifactId>
			<version>${junit.jupiter.version}</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
public class Junit5_Parameterized_Test {
	
	// This test will run sequentially 5 times with 1 argument each time
    @ParameterizedTest
    @ValueSource(ints = {8,4,2,6,10})
    void test_int_arrays(int arg) {
    	System.out.println("arg => "+arg);
        assertTrue(arg % 2 == 0);
    }   
}

Output :

arg => 8
arg => 4
arg => 2
arg => 6
arg => 10

Test results in eclipse :

junit 5 Parameterized test default run

1.2. Customizing Display Names in Parameterized tests

By default, the display name of a parameterized test invocation contains the invocation index and the String representation of all arguments for that specific invocation. The following placeholders are supported within custom display names.

PlaceholderDescription
{displayName}placeholder for the display name of the method.
{index}placeholder for the current invocation index (starts from 1).
{arguments}placeholder for the complete, comma-separated arguments list.
{0}{1}, …​placeholder for an individual argument.
public class Junit5_CustomDisplayNames_Test {
	
    @ParameterizedTest(name="#{index}- Test with Argument={arguments}")
    @ValueSource(ints = {8,4,2,6,10})
    void test_int_arrays(int arg) {
    	System.out.println("arg => "+arg);
        assertTrue(arg % 2 == 0);
    }  
    
    @ParameterizedTest(name="#{index} - Test with Argument={0}")
    @ValueSource(strings = {"Peter King", "Arthur King", "Martin King"})
    void test_string_arrays(String arg) {
    	String searchKey = "King";
    	
    	System.out.println("arg => "+arg);
        assertTrue(arg.contains(searchKey));
    }  
    
    @ParameterizedTest(name="Test with argument - {arguments}")
    @NullSource
    void test_null_source(String arg) {
    	
    	System.out.println("arg => "+arg);
        assertTrue(arg == null);
    }   
}

Test Results in eclipse :

junit-5-parameterized-tests-custom-display-name

2. Argument sources for parameterized tests

  1. @ValueSource
  2. @EnumSource
  3. @MethodSource
  4. @CsvSource
  5. @CsvFileSource
  6. @ArgumentSource

2.1. @ValueSource arguments

2.1.1. @ValueSource lets you specify a single array of literal values and can only be used for providing a single argument per parameterized test invocation.

2.1.2. We can pass empty or null values into the test via @EmptySource@NullSource or @NullAndEmptySource (since JUnit 5.4).

2.1.3. Only the following types of literal values are supported by @ValueSource.

  1. short – use shorts attribute to declare values
  2. byte –  use bytes attribute to declare values
  3. int –  use ints attribute to declare values
  4. long  –  use longs attribute to declare values
  5. float –  use floats attribute to declare values
  6. double –  use doubles attribute to declare values
  7. char –  use chars attribute to declare values
  8. java.lang.String –  use strings attribute to declare values
  9. java.lang.Class –  use classes attribute to declare values
public class Junit5_Valuesource_Test {
	
    @ParameterizedTest
    @ValueSource(ints = {8,4,2,6,10})
    void test_int_arrays(int arg) {
    	System.out.println("arg => "+arg);
        assertTrue(arg % 2 == 0);
    }  
    
    @ParameterizedTest(name="#{index} - Test with Argument={0}")
    @ValueSource(strings = {"Peter King", "Arthur King", "Martin King"})
    void test_string_arrays(String arg) {
    	String searchKey = "King";
    	
    	System.out.println("arg => "+arg);
        assertTrue(arg.contains(searchKey));
    }  
    
    @ParameterizedTest(name="#{index} - Test with Argument={0}")
    @NullSource
    void test_null_source(String arg) {
    	
    	System.out.println("arg => "+arg);
        assertTrue(arg == null);
    } 
    
    @ParameterizedTest(name="#{index} - Test with Argument={0}")
    @EmptySource
    void test_empty_source(String arg) {
    	
    	System.out.println("arg => "+arg);
        assertTrue(arg.isEmpty());
    } 
    
    @ParameterizedTest
    @NullAndEmptySource
    @ValueSource(strings = { " ", "   ", "\t", "\n" })
    void test_nullAndEmpty_source(String arg) {
        assertTrue(arg == null || arg.trim().isEmpty());
    }
}

2.2. @EnumSource arguments

  1. To run a test with values from enumeration@EnumSource provides a convenient way to use Enum constants.
  2. The annotation’s value attribute is optional. When omitted, the declared type of the first method parameter is used.
  3. The annotation provides an optional names attribute that lets you specify which constants shall be used to test.
  4. By default, the names will only keep the matched Enum constants. We can turn this around by setting the mode attribute, which supports EXCLUDE, INCLUDE, MATCH_ALL, MATCH_ANY .
public class Junit5_EnumSource_Test {
	
	enum Role {
        ADMIN, SUBSCRIBER, OBSERVER, AUTHOR, PUBLISHER, ANONYMOUS
    }
	
    @ParameterizedTest
    @EnumSource(Role.class)
    void testWith_EnumSource(Role role) {
    	System.out.println("arg => "+role);
        assertNotNull(role);
    }  
    
    @ParameterizedTest
    @EnumSource(value = Role.class, names = {"AUTHOR", "SUBSCRIBER"})
    void testWith_EnumSource_include(Role role) {
    	System.out.println("arg => "+role);
        assertNotNull(role);
    } 
    
    @ParameterizedTest
    @EnumSource(value = Role.class, mode = Mode.EXCLUDE, names = {"AUTHOR", "SUBSCRIBER"})
    void testWith_EnumSource_exclude(Role role) {
    	System.out.println("arg => "+role);
        assertNotNull(role);
    } 
    
    @ParameterizedTest
    @EnumSource(value = Role.class, mode = Mode.MATCH_ALL, names = "^(A|S).*R$")
    void testWith_EnumSource_Regex(Role role) {
    	System.out.println("---------");
    	System.out.println("arg => "+role);
        assertNotNull(role);
    }  
}

2.3. @MethodSource arguments

  1. By using @valueSource or @EnumSource it’s not possible to pass complex types as argument, @MethodSource allow us a factory method of the test class or external classes provide complex objects in test arguments.
  2. Factory methods should be static and must not accept any arguments.
  3. Each factory method must generate a stream of arguments and each set of values will be provided to the arguments of test method.
  4. If you do not explicitly provide a factory method name via @MethodSource, JUnit Jupiter will search for a factory method that has the same name as the current @ParameterizedTest method by convention.
public class Junit5_MethodSource_Test {
	
    @ParameterizedTest
    @MethodSource("stringProvider")
    void testWith_MethodSource(String arg) {
    	System.out.println("testWith_MethodSource(arg) => "+arg);
        assertNotNull(arg);
    }  
    
    @ParameterizedTest
    @MethodSource
    void testWith_Default_local_MethodSource(String arg) {
    	System.out.println("testWith_Default_local_MethodSource(arg) => "+arg);
        assertNotNull(arg);
    }  
    
    @ParameterizedTest(name="#{index} - Test with Argument={0},{1},{2}")
    @MethodSource("stringIntAndListProvider")
    void testWith_Multiple_MethodSource(String str, int num, List<String> list) {
    	System.out.println("str => "+str+"; num =>"+num+"; list => "+list);
    	assertTrue(str.length() >= 0);
        assertTrue(num >=1 && num <=3);
        assertTrue(list.size() >= 1);
    }  
    
    @ParameterizedTest(name="#{index} - Test with Argument={0},{1},{2}")
    @MethodSource("com.javabydeveloper.parameterized.external.ExternalArgsProvider#nameIdRoleProvider")
    void testWith_Multiple_ExternalMethodSource(String str, int num, String role) {
    	System.out.println("str => "+str+"; num =>"+num+"; role => "+role);
    	assertTrue(str.length() >= 0);
        assertTrue(num >=1 && num <=3);
        assertTrue(!role.isEmpty());
    }
    
    static Stream<String> stringProvider() {
        return Stream.of("admin", "subscriber", "author","anonymous");
    }
    
    static Stream<String> testWith_Default_local_MethodSource() {
        return Stream.of("Peter", "Philip", "John");
    }
    
    static Stream<Arguments> stringIntAndListProvider() {
        return Stream.of(
            arguments("Peter", 1, Arrays.asList("admin", "author")),
            arguments("John", 2, Arrays.asList("subscriber")),
            arguments("Philip", 3, Arrays.asList("Author", "Publisher"))
        );
    }      
}
public class ExternalArgsProvider {

	static Stream<Arguments> nameIdRoleProvider() {
        return Stream.of(
            arguments("Peter", 1, "admin"),
            arguments("John", 2, "author"),
            arguments("Philip", 3, "subscriber")
        );
    }
}

Output :

testWith_Default_local_MethodSource(arg) => Peter
testWith_Default_local_MethodSource(arg) => Philip
testWith_Default_local_MethodSource(arg) => John
testWith_MethodSource(arg) => admin
testWith_MethodSource(arg) => subscriber
testWith_MethodSource(arg) => author
testWith_MethodSource(arg) => anonymous
str => Peter; num =>1; role => admin
str => John; num =>2; role => author
str => Philip; num =>3; role => subscriber
str => Peter; num =>1; list => [admin, author]
str => John; num =>2; list => [subscriber]
str => Philip; num =>3; list => [Author, Publisher]

2.4. @CsvSource arguments

  1. @ValueSource or @EnumSource does not allow you to provide multiple arguments, @CsvSource allows you to express argument lists as comma-separated values.
  2. The @CsvSource accepts an array of comma-separated values and each array entry corresponds to a line in a CSV file.
public class Junit5_CSVSource_Test {
	
    @ParameterizedTest
    @CsvSource({
    	"Peter, admin, 1",
    	"John, author, 2",
    	"Martin, subscriber, 3"
    })
    void testWith_CsvSource(String name, String role, long id) {
    	System.out.println("testWith_CsvSource: name => "+name+"; role => "+role+"; id => "+id);
    	assertTrue(name.length() >= 0);
        assertTrue(id >=1 && id <=3);
        assertTrue(!role.isEmpty());
    }  
    
    @ParameterizedTest
    @CsvSource({
    	"Peter, admin, 1",
    	"John, author, 2",
    	"Martin, , 3"
    })
    void testWith_CsvSource_with_null(String name, String role, long id) {
    	System.out.println("name => "+name+"; role => "+role+"; id => "+id);
    	assertTrue(name.length() >= 0);
        assertTrue(id >=1 && id <=3);
        assertTrue(role != null);
    } 
    
    // @CsvSource(value = { "Peter, NIL, 1" }, nullValues = "NIL")
    // above line provide arguments => Peter, null, 1 (Since Junit 5.6)
}

Output :

testWith_CsvSource: name => Peter; role => admin; id => 1
testWith_CsvSource: name => John; role => author; id => 2
testWith_CsvSource: name => Martin; role => subscriber; id => 3
name => Peter; role => admin; id => 1
name => John; role => author; id => 2
name => Martin; role => null; id => 3

2.5. @CsvFileSource arguments

  1. If we have to provide large number of sets as arguments to set comma-separated values, @CsvFileSource lets you use CSV files from the classpath instead using @CsVSource. Following are the CSV file values.
NameRoleId
Peteradmin1
Johnauthor2
Martinsubscriber3
public class Junit5_CSVFileSource_Test {
	
    @ParameterizedTest
    @CsvFileSource(resources = "/users-data.csv", numLinesToSkip = 1)
    void testWith_MethodSource(String name, String role, long id) {
    	System.out.println("name => "+name+"; role => "+role+"; id => "+id);
    	assertTrue(name.length() >= 0);
        assertTrue(id >=1 && id <=3);
        assertTrue(!role.isEmpty());
    }          
}

Output :

name => Peter; role => admin; id => 1
name => John; role => author; id => 2
name => Martin; role => subscriber; id => 3

2.6. @ArgumentsSource – Custom Argument Provider

To support advanced approach, Junit 5 supports a custom, reusable ArgumentProvider to provide test arguments.

Following is the Custom Argument provider.

public class CustomArgumentProvider implements ArgumentsProvider{

	@Override
	public Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception {
		
		 return Stream.of(
		            arguments("Peter", 1, Arrays.asList("admin", "author")),
		            arguments("John", 2, Arrays.asList("subscriber")),
		            arguments("Philip", 3, Arrays.asList("Author", "Publisher"))
		        );
	}
}

Using CustomArgumentProvider in test case.

public class Junit5_ArgumentSource_Test {
	
    @ParameterizedTest
    @ArgumentsSource(CustomArgumentProvider.class)
    void testWith_implicit_conversion(String str, int num, List<String> list) {
    	System.out.println("str => "+str+"; num =>"+num+"; list => "+list);
    	assertTrue(str.length() >= 0);
        assertTrue(num >=1 && num <=3);
        assertTrue(list.size() >= 1);
    }           
}

Output :

str => Peter; num =>1; list => [admin, author]
str => John; num =>2; list => [subscriber]
str => Philip; num =>3; list => [Author, Publisher]

3. Argument conversion

3.1. Implicit conversion

JUnit Jupiter provides a number of built-in implicit type converters. The conversion process depends on the declared type of each method parameter. The conversion process depends on the declared type of each method parameter.  For example if you provide parameterized test annotated with @ValueSource(ints = { 2, 5, 7 })  , method argument can be int, longfloat, or double , Junit jupiter supports widening primitive conversion.

Also Junit supports string to following are the few target types for example.

  1. boolean/Boolean – example -> "true" → true
  2. Enum subclass – example -> "SECONDS" → TimeUnit.SECONDS
  3. java.time.LocalDate – example -> "2017-03-14" → LocalDate.of(2017, 3, 14)
  4. java.util.Locale – example -> "en" → new Locale("en")
 @ParameterizedTest
	 @ValueSource(strings = {"2017-03-14", "2020-01-20"})
	 void testWith_implicit_conversion(LocalDate localDate) {
	    System.out.println("localDate => "+localDate.toString());
	    assertTrue(localDate != null);
	 
	 } 

Output :

localDate => 2017-03-14
localDate => 2020-01-20

3.2. Explicit conversion

Instead of relying on implicit conversion, we can create explicit converter specifying ArgumentConverter and by using @ConvertWith annotation like in the following example.

3.2.1 Create Argument Converter

public class DateFormatArgumentCoverter extends SimpleArgumentConverter {

    @Override
    protected Object convert(Object source, Class<?> targetType) {
    	DateTimeFormatter format = DateTimeFormatter.ofPattern("MM-dd-yyyy");      
        return LocalDate.parse(source.toString(), format);
    }
}

3.2.2 Explicit argument converter using @ConvertWith

@ParameterizedTest
	 @ValueSource(strings = {"03-21-2017", "01-28-2020"})
	 void testWith_explicit_conversion(
			 @ConvertWith(DateFormatArgumentCoverter.class) LocalDate localDate) {
	    System.out.println("localDate => "+localDate.toString());
	    assertTrue(localDate != null);
	 
	 } 

4. Argument Aggregation for Junit 5 Parameterized tests

4.1. ArgumentsAccessor

By default, each argument provided to a @ParameterizedTest method corresponds to a single method parameter. ArgumentsAccessor aggregates set of provided arguments and can be accessed those arguments in a type-safe manner with support for automatic type conversion.

@ParameterizedTest
    @CsvSource({
    	"Peter, admin, 1, 2017-03-14",
    	"John, author, 2, 2017-03-14",
    	"Martin, subscriber, 3, 2017-03-14"
    })
    void testWith_ArgumentAccessor(ArgumentsAccessor accessor) {
		User user = new User(accessor.getString(0),
				accessor.getLong(2),
				accessor.getString(1),
				accessor.get(3, LocalDate.class));

    	System.out.println("name => "+user.getName()+
    			"; role => "+user.getRole()+
    			"; id => "+user.getId());

    	assertTrue(user.getName().length() >= 0);
        assertTrue(user.getId() >=1 && user.getId() <=3);
        assertTrue(!user.getRole().isEmpty());
    } 

Output :

name => Peter; role => admin; id => 1
name => John; role => author; id => 2
name => Martin; role => subscriber; id => 3

4.2. Custom Argument Aggregators

If you have large number of arguments, accessing arguments using ArgumentsAccessor will become complex and messy. We can use custom aggregators that supported by Junit jupiter. To use a custom aggregator,

  1. Implement the ArgumentsAggregator interface .
  2. Register it via the @AggregateWith annotation
  3. Use it with @ParameterizedTest method.

Create Custom ArgumentAggregator :

public class User {
	
	private String name;
	private long id;
	private String role;
	private LocalDate dateOfBirth;
	
	public User(String name, long id, String role, LocalDate dateOfBirth) {
		super();
		this.name = name;
		this.id = id;
		this.role = role;
		this.dateOfBirth = dateOfBirth;
	}
    // Setters and getters
}

UserAggregator.java

public class UserAggregator implements ArgumentsAggregator {
	
    @Override
    public User aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) {
    	return new User(accessor.getString(0),
				accessor.getLong(2),
				accessor.getString(1),
				accessor.get(3, LocalDate.class));
    }
}

Use UserAggregator.java to a test case :

@ParameterizedTest
    @CsvSource({
    	"Peter, admin, 1, 2017-03-14",
    	"John, author, 2, 2017-03-14",
    	"Martin, subscriber, 3, 2017-03-14"
    })
    void testWith_ArgumentAggregator(@AggregateWith(UserAggregator.class) User user) {
    	System.out.println("name => "+user.getName()+
    			"; role => "+user.getRole()+
    			"; id => "+user.getId()+
    			"; dateOfBirth => "+user.getDateOfBirth().toString());
    	assertTrue(user.getName().length() >= 0);
        assertTrue(user.getId() >=1 && user.getId() <=3);
        assertTrue(!user.getRole().isEmpty());
    }

Output :

name => Peter; role => admin; id => 1; dateOfBirth => 2017-03-14
name => John; role => author; id => 2; dateOfBirth => 2017-03-14
name => Martin; role => subscriber; id => 3; dateOfBirth => 2017-03-14

Conclusion

In this article we have seen what are the Junit 5 Parameterized tests, how to create parameterized test cases, several kind of argument sources for parameterized tests supported by Junit 5, argument conversion and arguments aggregators with several examples.

You can checkout source code from our github repository.

You also might be interested in following examples :

  1. Junit 5 Dynamic Tests and @TestFactory annotation.
  2. Junit 5 tags and filter test cases for execution.
  3. Junit 5 Timeout tests, fail tests if not completed within time.
  4. Junit 5 Repeated tests and display Repetition info.
  5. Execute Junit 5 tests parallel and @ResourceLock examples
  6. Creating a Custom ParameterResolver and built in Junit 5 Parameter Resolvers.

Satish Varma
Satish Varmahttps://javabydeveloper.com
Satish is post graduated in master of computer applications and experienced software engineer with focus on Spring, JPA, REST, TDD and web development. Also founder of javabydeveloper.com. Follow him on LinkedIn or Twitter or Facebook

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Stay in Touch

Categories