Spring Boot uses the @parameterizedTest annotation provided by JUnit5 to implement ParameterizedTest and configure parameter sources with other annotations.
Custom test execution name
@parameterizedTest The default test execution name is in the format of parameter 1=XXX, parameter 2=YYY… , you can customize the test execution name by modifying the name attribute.
@parameterizedTest (name = "{index}") {0}") @ValueSource(ints = { 1, 10, 100 }) public void test(int value) { Assertions.assertTrue(value < 100); }Copy the code
The test execution name is:
Test 1, Parameter: 1 Test 2, parameter: 10 Test 3, parameter: 100Copy the code
If there are multiple parameters, they are: {0}, {1}, {2}…
Parameter data source
1. @ValueSource
The @Valuesource data source supports arrays of the following types:
short
byte
int
long
float
double
char
java.lang.String
java.lang.Class
For example, if the int type is set to 100, the test result is Fail.
@ParameterizedTest @ValueSource(ints = { 1, 10, 100 }) public void test(int value) { Assertions.assertTrue(value < 100); }Copy the code
2. @NullSource
Null is sometimes used when a string is used as an input parameter. Null cannot be written directly to the strings array of the @Valuesource annotation (the compiler raises an error).
@ValueSource(strings = { null, "X", "Y", "Z" })
Copy the code
The correct way is to use the @nullSource annotation.
@ParameterizedTest
@NullSource
@ValueSource(strings = { "X", "Y", "Z" })
public void test(String value) {
System.out.println("Param: " + value);
}
Copy the code
3. @EmptySource
Similar to @nullSource, you can use @emptysource if you want to use an empty string when using a string as an input parameter.
@ParameterizedTest
@EmptySource
@ValueSource(strings = { "X", "Y", "Z" })
public void test(String value) {
System.out.println("Param: " + value);
}
Copy the code
Unlike @nullSource, you can write an empty string argument directly into the strings array annotated by @Valuesource without the compiler reporting an error.
@ValueSource(strings = { "", "X", "Y", "Z" })
Copy the code
4. @NullAndEmptySource
If you want to use both null and an empty string as test method input arguments, you can use the @nullandemptysource annotation.
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = { "X", "Y", "Z" })
public void test(String value) {
System.out.println("Param: " + value);
}
Copy the code
5. @EnumSource
The @EnumSource is an enumeration data source that allows all or some of the values in an enumeration class to be used as inputs to test methods.
- Start by defining an enumerated class
public enum Type {
ALPHA, BETA, GAMMA, DELTA
}
Copy the code
- Add to the test method
@EnumSource
Note that JUnit determines which enumeration to use based on the test method parameter type.
@ParameterizedTest
@EnumSource
public void test(Type type) {
System.out.println("Param: " + type);
}
Copy the code
- If you want to use only some of the values in the enumeration, you can use the
@EnumSource
annotationsnames
Property, ifnames
Property contains enumeration values that do not exist, an error is reported at runtime.
@ParameterizedTest
@EnumSource(names = { "BETA", "DELTA" })
public void test(Type type) {
System.out.println("Param: " + type);
}
Copy the code
- It can also be set
@EnumSource
annotationsmode
Properties forEnumSource.Mode.EXCLUDE
Specifies which enumerated values are not to be used.
@ParameterizedTest
@EnumSource(mode = Mode.EXCLUDE, names = { "BETA", "DELTA" })
public void test(Type type) {
System.out.println("Param: " + type);
}
Copy the code
6. @MethodSource
The @methodSource annotation can specify a method name that uses the collection of elements returned by the method as an input parameter to the test method. The method must return a Stream type.
@ParameterizedTest
@MethodSource("paramProvider")
public void test(String param) {
System.out.println("Param: " + param);
}
public static Stream<String> paramProvider() {
return Stream.of("X", "Y", "Z");
}
Copy the code
The above is a static method, and this method is in the same class as the test method, if not in the same class you need to specify the full path of the static method: package. Class name # Method name.
@ParameterizedTest
@MethodSource("com.example.demo.DemoUnitTest#paramProvider")
public void test(String param) {
System.out.println("Param: " + param);
}
Copy the code
If the parameter data source is not a static method but an instance method, you need to use the @testInstance annotation.
7. @CsvSource
All of the above data sources are for one-parameter test methods. @csvSource can handle multiple parameter test methods.
@ParameterizedTest
@CsvSource({
"2021/12/01, Wednesday, Sunny",
"2021/12/10, Friday, Rainy",
"2021/12/13, Monday, Chilly"
})
public void test(String date, String dayOfWeek, String weather) {
System.out.println(date + " -- " + dayOfWeek + " -- " + weather);
}
Copy the code
The @csvSource annotation provides a nullValues attribute that can replace a specified string with NULL.
@ParameterizedTest
@CsvSource(value = {
"2021/12/01, Wednesday, Sunny",
"2021/12/10, Friday, ",
"2021/12/13, Monday, Chilly"
}, nullValues = "")
public void test(String date, String dayOfWeek, String weather) {
System.out.println(date + " -- " + dayOfWeek + " -- " + weather);
}
Copy the code
8. @CsvFileSource
When the test data is large, it is not appropriate to write the test data directly to the source file. You can use @Csvfilesource instead of @csvSource, specify the corresponding CSV file as the data source, and use the numLinesToSkip attribute to specify the number of lines to skip (skip the table header).
@ParameterizedTest
@CsvFileSource(files = "src/test/resources/data.csv", numLinesToSkip = 1, delimiter = ',', nullValues = "")
public void test(String date, String dayOfWeek, String weather) {
System.out.println(date + " -- " + dayOfWeek + " -- " + weather);
}
Copy the code
The CSV file is as follows:
Date, week, weather 2021/12/1, Wednesday, Friday, Sunny 2021/12/10, 2021/12/13, Monday, ChillyCopy the code
9. @ArgumentsSource
If none of the above data sources meet your testing requirements, you can develop an implementation class that implements the ArgumentsProvider interface as a custom parameter data source.
package com.example.demo; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; public class CustomArgumentsProvider implements ArgumentsProvider { @Override public Stream<? extends Arguments> provideArguments(ExtensionContext arg0) throws Exception { return Stream.of(Arguments.of(1), Arguments.of("2"), Arguments.of(true)); }}Copy the code
Then use @ArgumentsSource to specify the data source.
@ParameterizedTest
@ArgumentsSource(CustomArgumentsProvider.class)
public void test(Object param) {
System.out.println(param);
}
Copy the code
Parameter conversion
If the data type of the parameter data source is inconsistent with the data type of the test method parameter, you can specify a type converter for the data type conversion.
@ParameterizedTest
@ValueSource(strings = { "2021/12/01", "2021/12/10" })
public void test(@JavaTimeConversionPattern("yyyy/MM/dd") LocalDate date) {
System.out.println(date);
}
Copy the code
The above code converts the string type of the data source to the date type through a converter.
1. Customize the parameter converter
Custom argument converters need to implement the ArgumentConverter interface.
package com.example.demo;
import java.util.regex.Pattern;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.ArgumentConverter;
public class CustomConversionPattern implements ArgumentConverter {
@Override
public Object convert(Object arg0, ParameterContext arg1) throws ArgumentConversionException {
boolean isInteger = Pattern.matches("^[1-9][0-9]*$", arg0.toString());
if (!isInteger) {
throw new IllegalArgumentException();
}
return Integer.valueOf(arg0.toString());
}
}
Copy the code
Use the @convertWith annotation to customize the parameter converter before testing method parameters.
@ParameterizedTest
@ValueSource(strings = { "1", "10", "100" })
public void test(@ConvertWith(CustomConversionPattern.class) Integer value) {
System.out.println(value);
}
Copy the code
Fourth, field aggregation
If each data source has multiple fields, it is very inconvenient to define an argument equal to the number of fields in the test method as shown above. You can get all the fields in the data source by using ArgumentsAccessor. The CSV field is actually stored in an Object array inside the ArgumentsAccessor instance.
@ParameterizedTest
@CsvSource({
"2021/12/01, Wednesday, Sunny",
"2021/12/10, Friday, Rainy",
"2021/12/13, Monday, Chilly"
})
public void test(ArgumentsAccessor argumentsAccessor) {
LocalDate date = LocalDate.parse(argumentsAccessor.getString(0), DateTimeFormatter.ofPattern("yyyy/MM/dd"));
String dayOfWeek = argumentsAccessor.getString(1);
String weather = argumentsAccessor.getString(2);
System.out.println(date + " -- " + dayOfWeek + " -- " + weather);
}
Copy the code
1. Custom parameter aggregator
Encapsulate each piece of data in CSV into a class object.
package com.example.demo; import java.time.LocalDate; public class TestData { private LocalDate date; private String dayOfWeek; private String weather; // getsetter omitted}Copy the code
Custom aggregators.
package com.example.demo; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.params.aggregator.ArgumentsAccessor; import org.junit.jupiter.params.aggregator.ArgumentsAggregationException; import org.junit.jupiter.params.aggregator.ArgumentsAggregator; public class TestDataAggregator implements ArgumentsAggregator { @Override public Object aggregateArguments(ArgumentsAccessor arg0, ParameterContext arg1) throws ArgumentsAggregationException { TestData testData = new TestData(); testData.setDate(LocalDate.parse(arg0.getString(0), DateTimeFormatter.ofPattern("yyyy/MM/dd"))); testData.setDayOfWeek(arg0.getString(1)); testData.setWeather(arg0.getString(2)); return testData; }}Copy the code
Specify the aggregator using the @Aggregatewith annotation.
package com.example.demo; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.aggregator.AggregateWith; import org.junit.jupiter.params.provider.CsvSource; public class DemoUnitTest { @ParameterizedTest @CsvSource({ "2021/12/01, Wednesday, Sunny", "2021/12/10, Friday, Rainy", "2021/12/13, Monday, Chilly" }) public void test(@AggregateWith(TestDataAggregator.class) TestData testData) { System.out.println(testData.getDate() + " -- " + testData.getDayOfWeek() + " -- " + testData.getWeather()); }}Copy the code
2. Custom aggregator annotations
Encapsulate @AggregateWith(testDataAggregator.class) as a custom annotation from the previous step.
package com.example.demo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.params.aggregator.AggregateWith;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AggregateWith(TestDataAggregator.class)
public @interface CsvToTestData {
}
Copy the code
Use custom aggregator annotations.
@ParameterizedTest
@CsvSource({
"2021/12/01, Wednesday, Sunny",
"2021/12/10, Friday, Rainy",
"2021/12/13, Monday, Chilly"
})
public void test(@CsvToTestData TestData testData) {
System.out.println(testData.getDate() + " -- " + testData.getDayOfWeek() + " -- " + testData.getWeather());
}
Copy the code