I have unit tests using TestNG that I try to move to JUnit Jupiter (JUnit 5), and I wonder which is the best way to do:
TestNG:
@DataProvider
public Object[][] invalidPortNumbers() {
return new Object[][] {
{ "--http", "" },
{ "--http", "-42" },
{ "--http", "0" },
{ "--http", "not_a_port_number" },
{ "--https", "67000" }
};
}
@Test(dataProvider = "invalidPortNumbers",
expectedExceptions = ParameterException.class,
expectedExceptionsMessageRegExp = ".* is not valid port number .*")
public void shouldFailToValidatePortNumber(final String... args) {
new CommandLineParser(args);
}
I saw that moving to JUnit Jupiter, I can do:
static Stream<Arguments> invalidPortNumbers2() {
return Stream.of(
Arguments.of((Object) new String[] { "--http", "-42" }),
Arguments.of((Object) new String[] { "--http", "0" }),
Arguments.of((Object) new String[] { "--http", "not_a_port_number" }),
Arguments.of((Object) new String[] { "--https", "67000" })
);
}
@ParameterizedTest
@MethodSource("invalidPortNumbers2")
void shouldFailToValidatePortNumber(final String... args) {
assertThatThrownBy(() -> new CommandLineParser(args))
.isInstanceOf(ParameterException.class)
.hasMessageMatching(".* is not valid port number .*");
}
Is there any other way to simplify this and keep the previous dataProvider structure to minimise the changes?
Thanks.
With parameterized tests in JUnit Jupiter, if the return type of the method referenced via @MethodSource
is a 2 dimensional array, the values of the inner arrays will be passed as multiple arguments to a single test method invocation. This means that there is no straightforward way to migrate a test method that accepts var-args (or an explicit array) from TestNG's @DataProvider
to JUnit Jupiter's @MethodSource
.
Your invalidPortNumbers2()
is a suitable workaround for this limitation, but there are other workarounds that you may prefer.
Updated Answer:
The simplest way to process all arguments as a single array is via the ArgumentsAccessor
API.
If you make your existing invalidPortNumbers()
method static
, you can use it "as is" and convert the arguments to an array as follows.
@ParameterizedTest
@MethodSource("invalidPortNumbers")
void shouldFailToValidatePortNumber(ArgumentsAccessor accessor) {
Object[] args = accessor.toArray();
// Use args ...
}
Though, in JUnit Jupiter you might find using a @CsvSource
preferable to using the @MethodSource
for such use cases. So, to achieve the same goal, you can rewrite your test as follows and get rid of the invalidPortNumbers()
method.
@ParameterizedTest
@CsvSource({
"--http, ''",
"--http, -42",
"--http, 0",
"--http, not_a_port_number",
"--https, 67000"
})
void shouldFailToValidatePortNumber(ArgumentsAccessor accessor) {
Object[] args = accessor.toArray();
// Use args ...
}
Original Answer:
For starters, the following utility method will help to simplify things (declared in a class named VarArgsParamsTests
but could be moved to a common utility class). Note that arguments()
is statically imported via org.junit.jupiter.params.provider.Arguments.arguments
.
static Arguments arrayArguments(String... array) {
return arguments((Object) array);
}
Given that, if you want to keep your existing invalidPortNumbers()
method with as little modification as possible, you can redefine its signature as static String[][] invalidPortNumbers()
and introduce the following method that you actually reference from @MethodSource
.
static Stream<Arguments> invalidPortNumbersArguments() {
return Arrays.stream(invalidPortNumbers()).map(VarArgsParamsTests::arrayArguments);
}
If you're willing to modify your existing invalidPortNumbers()
method more, you could change it to the following that also uses the arrayArguments()
utility method.
static Stream<Arguments> invalidPortNumbers() {
return Stream.of(
arrayArguments("--http", ""),
arrayArguments("--http", "-42"),
arrayArguments( "--http", "0"),
arrayArguments( "--http", "not_a_port_number"),
arrayArguments( "--https", "67000")
);
}