junit-team / junit5

✅ The 5th major version of the programmer-friendly testing framework for Java and the JVM
https://junit.org
Other
6.31k stars 1.46k forks source link

@ParameterizedTest @ArgumentSouce should accept Arguments[] and Stream<Arguments> #3796

Closed medwards closed 4 months ago

medwards commented 4 months ago

I'd like to use a list of Arguments or a stream directly as a source for a ParameterizedTest, without creating any additional methods or classes.

@ParameterizedTest
@ArgumentSource({
    Arguments.of("foo", false, "A"),
    Arguments.of("foo", true, "A"),
    Arguments.of("bar", false, "B"),
    Arguments.of("baz", true, "C")
})
void testABC(String firstInput, boolean secondInput, String expectedValue) {
    assertEquals(f(firstInput, secondInput), expectedValue);
}

The current alternatives I can find from docs and experimentation are:

The trade-offs using these are:

sormuras commented 4 months ago

Arbitrary object instances and annotations?

https://docs.oracle.com/javase/specs/jls/se22/html/jls-9.html#jls-9.6.1

[...] The return type of a method declared in the body of annotation interface must be one of the following, or a compile-time error occurs:

A primitive type

String

Class or an invocation of Class (§4.5)

An enum class type

An annotation interface type

An array type whose component type is one of the preceding types (§10.1). [...]

sormuras commented 4 months ago

Or is this proposal about an @Arguments annotation interface type?

medwards commented 4 months ago

Or is this proposal about an @Arguments annotation interface type?

I believe this is what I'm shooting for - I'm not sure what about the best approach - but a new Source annotation for this, or extending ValueSource to accept arguments also seem workable to me.

marcphilipp commented 4 months ago

So that would make it look like this, right?

@ParameterizedTest
@ArgumentSource({
    @Arguments({"foo", false, "A"}),
    @Arguments({"foo", true, "A"}),
    @Arguments({"bar", false, "B"}),
    @Arguments({"baz", true, "C"})
})
void testABC(String firstInput, boolean secondInput, String expectedValue) {
    assertEquals(f(firstInput, secondInput), expectedValue);
}

Actually that doesn't work since Object[] is not a valid type to use as an annotation attribute (see above JLS snippet), either.

sbrannen commented 4 months ago

I'd like to use a list of Arguments or a stream directly as a source for a ParameterizedTest, without creating any additional methods or classes.

That's simply not possible in Java: the Java language does not support arbitrary types as annotation attributes.

The current alternatives I can find from docs and experimentation are:

Have you seen the new @FieldSource support coming in JUnit Jupiter 5.11?

https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parameterized-tests-sources-FieldSource

@ParameterizedTest
@FieldSource("stringIntAndListArguments")
void testWithMultiArgFieldSource(String str, int num, List<String> list) {
    assertEquals(5, str.length());
    assertTrue(num >=1 && num <=2);
    assertEquals(2, list.size());
}

static List<Arguments> stringIntAndListArguments = Arrays.asList(
    arguments("apple", 1, Arrays.asList("a", "b")),
    arguments("lemon", 2, Arrays.asList("x", "y"))
);

That's the least amount of boilerplate that we can support in an annotation in Java.

You still have to create a field, but that's less "boilerplate" than creating a method.

medwards commented 4 months ago

Thanks everyone for the feedback - I didn't follow the implication of the EBNF in the JLS so didn't realize my use-case was excluded.

FieldSource looks like an improvement that I'll definitely use when it's available.

sbrannen commented 4 months ago

Thanks everyone for the feedback - I didn't follow the implication of the EBNF in the JLS so didn't realize my use-case was excluded.

No worries.

Probably all Java developers (including members of the JUnit team) wish that such things were possible with annotations.

FieldSource looks like an improvement that I'll definitely use when it's available.

Glad to hear it, and for the record... I'm also looking forward to using it actively in my own tests.

The example in the User Guide is required to use Java 8 (Arrays.asList), but on later versions of Java it becomes even more succinct (List.of). 😎

static List<Arguments> stringIntAndListArguments = List.of(
    arguments("apple", 1, List.of("a", "b")),
    arguments("lemon", 2, List.of("x", "y"))
);