In this post, We will talk and learn about Implicit and explicit Argument Conversion in ParameterizedTest Tests in JUnit Jupiter
- Widening Conversion
JUnit Jupiter supports Widening Primitive Conversion for arguments specified to a @ParameterizedTest
. For example, if a parameterized test annotated with @ValueSource(ints = { 1, 2, 3 })
can be declared to accept not only an argument of type int
but also an argument of type long
, float
, or double
.
- Implicit Conversion
To support use cases like
@CsvSource
, JUnit Jupiter provides a number of built-in implicit type converters. Usually, the conversion process basically depends on the declared type of each method argument.For example, if a
@ParameterizedTest
declares a parameter of typeTimeUnit
and the actual type supplied by the declared source is aString
, the string will be automatically converted into the correspondingTimeUnit
enum constant.12345@ParameterizedTest@ValueSource(strings = "SECONDS")void testWithImplicitArgumentConversion(ChronoUnit chronoUnit) {assertNotNull(chronoUnit.name());} - Fallback String-to-Object Conversion
In addition to implicit conversion from strings to the target types listed in the above table, JUnit Jupiter also provides a fallback mechanism for automatic conversion from a
String
to a given targetType if the targetType declares exactly one suitable factory method or a factory constructor.- factory method: a non-private,
static
a method declared in the target type that accepts a single argument and returns an instance of the target type. The name of the method can be anything and need not follow any particular convention method. - factory constructor: a non-private constructor in the target type that accepts a single argument. You should also note that the targetType should be declared as either a top-level class or as a
static
inner class.
If multiple factory methods found then they will be ignored. If a factory method and a factory constructor are discovered, the factory method will be used instead of the constructor. For example, in the following
@ParameterizedTest
method, theBook
the argument will be created by invoking theBook.fromTitle(String)
factory method and passing"Hibernate Compelete Reference"
as the title of the book.12345@ParameterizedTest@ValueSource(strings = "Hibernate Compelete Reference")void testWithImplicitFallbackArgumentConversion(Book book) {assertEquals("Hibernate Compelete Reference", book.getTitle());}12345678910111213141516public class Book {private final String title;private Book(String title) {this.title = title;}public static Book fromTitle(String title) {return new Book(title);}public String getTitle() {return this.title;}}Explicit Conversion
- factory method: a non-private,
- Instead of relying on implicit argument conversion, you may explicitly specify an
ArgumentConverter
to use for a certain parameter using the@ConvertWith
annotation like in the following example. You should note that an implementation ofArgumentConverter
must be declared as either a top-level class or as astatic
inner class.
1 2 3 4 5 6 7 |
@ParameterizedTest @EnumSource(ChronoUnit.class) void testCaseWithExplicitArgumentConversion( @ConvertWith(StringArgumentConverter.class) String arg) { assertNotNull(ChronoUnit.valueOf(arg)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.kkjavatutorials; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.params.converter.SimpleArgumentConverter; public class StringArgumentConverter extends SimpleArgumentConverter { @Override protected Object convert(Object sourceType, Class<?> targetType) { assertEquals(String.class, targetType, "It will only convert to String"); if (sourceType instanceof Enum<?>) { return ((Enum<?>) sourceType).name(); } return String.valueOf(sourceType); } } |
Let’s say converter is only meant to convert one type to another then you can extend TypedArgumentConverter
to avoid boilerplate type checks.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.kkjavatutorials; import org.junit.jupiter.params.converter.TypedArgumentConverter; public class LengthArgumentConverter extends TypedArgumentConverter<String, Integer> { protected LengthArgumentConverter() { super(String.class, Integer.class); } @Override protected Integer convert(String source) { return source.length(); } } |
The explicit argument converters are meant to be implemented by test and extension developer so junit-jupiter-params
only provides a single explicit argument converter that may be used as a reference implementation: JavaTimeArgumentConverter
. It is used via the composed annotation JavaTimeConversionPattern
.
1 2 3 4 5 6 7 |
@ParameterizedTest @ValueSource(strings = { "20.10.2021", "31.12.2021" }) void testCaseWithExplicitJavaTimeConverter( @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate localDate) { assertEquals(2021, localDate.getYear()); } |
Let’s try to understand the above concept using a demo project
pom.xml
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 29 30 31 32 33 34 35 36 37 38 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kkjavatutorials</groupId> <artifactId>JUnit5ArgumentConversionExample</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <maven.compiler.target>8</maven.compiler.target> <maven.compiler.source>8</maven.compiler.source> <junit.jupiter.version>5.6.2</junit.jupiter.version> </properties> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>${junit.jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>${junit.jupiter.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>${junit.jupiter.version}</version> <scope>test</scope> </dependency> </dependencies> </project> |
MyUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.kkjavatutorials; public class MyUtils { /** *Finding Square of input number * @param number * @return */ public static int squareOfNumber(int number) { return number*number; } } |
Book.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.kkjavatutorials.model; public class Book { private final String title; private Book(String title) { this.title = title; } public static Book creatBookFromTitle(String title) { return new Book(title); } public String getTitle() { return this.title; } } |
Employee.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.kkjavatutorials.model; public class Employee { private String name; private Integer salary; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getSalary() { return salary; } public void setSalary(Integer salary) { this.salary = salary; } } |
EmployeeType.java
1 2 3 4 5 6 |
package com.kkjavatutorials; public enum EmployeeType { ADMIN,MANANGER,DEVELOPER } |
StringArgumentConverter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.kkjavatutorials; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.params.converter.SimpleArgumentConverter; public class StringArgumentConverter extends SimpleArgumentConverter { @Override protected Object convert(Object sourceType, Class<?> targetType) { assertEquals(String.class, targetType, "It will only convert to String"); if (sourceType instanceof Enum<?>) { return ((Enum<?>) sourceType).name(); } return String.valueOf(sourceType); } } |
ExplicitConversionTest.java
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 29 30 31 32 |
package com.kkjavatutorials; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.JavaTimeConversionPattern; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; public class ExplicitConversionTest { @ParameterizedTest @EnumSource(ChronoUnit.class) void testCaseWithExplicitArgumentConversion( @ConvertWith(StringArgumentConverter.class) String arg) { assertNotNull(ChronoUnit.valueOf(arg)); } @ParameterizedTest @ValueSource(strings = { "20.10.2021", "31.12.2021" }) void testCaseWithExplicitJavaTimeConverter( @JavaTimeConversionPattern("dd.MM.yyyy") LocalDate localDate) { assertEquals(2021, localDate.getYear()); } } |
MyUtilsTest.java
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
package com.kkjavatutorials; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.converter.ConvertWith; import org.junit.jupiter.params.converter.JavaTimeConversionPattern; import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.ValueSource; import com.kkjavatutorials.model.Book; /** * Argument Conversion in Parameterized Tests Examples * @author KK JavaTutorials */ public class MyUtilsTest { /** * Widening Conversion by default */ @ParameterizedTest @ValueSource(ints = { 10, 20, 30 }) void wideningPrimitiveConversionTest(int numberArg) { long compare = 1000L; assertTrue(MyUtils.squareOfNumber(numberArg) < compare); } /** * Here string will be automatically converted into the corresponding TimeUnit * enum constant Implicit Conversion * @param arg */ @ParameterizedTest @ValueSource(strings = {"SECONDS","MILLIS"}) void testWithImplicitArgumentConversion(ChronoUnit arg) { assertNotNull(arg.name()); } /** * Fallback String-to-Object Conversion in ParameterizedTest * @param book */ @ParameterizedTest @ValueSource(strings = { "Head First Java", "Effective Java" }) void testWithImplicitFallbackArgumentConversion(Book book) { assertNotNull(book.getTitle()); } /** * Explicit Argument Conversion in ParameterizedTest * @param argName */ @ParameterizedTest @EnumSource(EmployeeType.class) void testWithExplicitArgumentConversion(@ConvertWith(StringArgumentConverter.class) String argName) { assertNotNull(argName); } @ParameterizedTest @ValueSource(strings = {"10-01-2020", "31-08-2020" }) void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern("dd-MM-yyyy") LocalDate localDate) { assertEquals(2020, localDate.getYear()); } } |
The output of the above project:
You May Also Like:
Junit 5 Architecture
JUnit 5 Annotations
JUnit 5 Maven Dependency
JUnit 5 with Gradle Dependency
JUnit 5 Test Lifecycle
JUnit 5 @BeforeAll annotation example
Unit 5 @AfterAll annotation example
JUnit 5 @BeforeEach and @AfterEach annotation Example
JUnit 5 Display Names
Assertions in JUnit 5 Examples
Third-party Assertion Libraries support in JUnit 5
JUnit 5 Assumptions Examples
Conditional Test Execution in JUnit 5
JUnit 5 Nested Tests Example
JUnit 5 @Tag Annotation example
Test Execution Order in Junit 5
Dependency Injection and Testing in JUnit 5
Test Interfaces and Default Methods in JUnit 5
That’s all about Argument Conversion in Parameterized Tests in Junit 5
If you have any feedback or suggestion please feel free to drop in below comment box.