7

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.

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
V-O
  • 123
  • 1
  • 5
  • What happens if you make your existing `invalidPortNumbers()` method `static` and reference it from `@MethodSource`? – Sam Brannen Nov 13 '20 at 21:47
  • I got the following exception: `org.junit.jupiter.api.extension.ParameterResolutionException: Error converting parameter at index 0: No implicit conversion to convert object of type java.lang.String to type [Ljava.lang.String;` – V-O Nov 15 '20 at 08:07

1 Answers1

5

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")
    );
}
Sam Brannen
  • 29,611
  • 5
  • 104
  • 136