Since JUnit 5.9.0 it's theoretically possible to have a @MethodSource with a parameter, but the the syntax is different than the one you suggested (it would be great to have it like that) and it requires a significant amount of code and consideration to use it. The documentation says:
Factory methods can declare parameters, which will be provided by registered implementations of the ParameterResolver extension API.
What you would need:
- An implementation of
ParameterResolver.
- Register that implementation as an
Extension.
- Ensure, that your
ParameterResolver implementation does not accidentally interfere with other parameterised test methods, as JUnit would automatically try to use it for them as soon as supportsParameter returns true.
So I will skip providing a complete example here (you find a basic example in the documentation), but suggest different approaches additional to the one previously suggested by Nestor Milyaev, which is definitely the most simple approach.
Using @TestFactory
Given a code snippet very similar to yours (with annotations removed)...
void testWithDifferentArguments(int argument) {
assertNotEquals(9, argument);
}
static IntStream myFactoryMethod(int end) {
return IntStream.range(0, end);
}
...one could write (syntax for JUnit 5.8.0+)...
@TestFactory
@DisplayName("my awesome dynamic test")
Stream<DynamicTest> dynamicTests() {
return DynamicTest.stream(
myFactoryMethod(10).mapToObj(i -> named("my awesome dynamic test using " + i, i)),
this::testWithDifferentArguments);
}
Or alternatively in JUnit 5.0.0 syntax:
@TestFactory
@DisplayName("my awesome dynamic test")
Stream<DynamicTest> dynamicTests() {
return DynamicTest.stream(
myFactoryMethod(10).iterator(),
i -> "my awesome dynamic test using " + i,
this::testWithDifferentArguments);
}
Using @ArgumentsSource with custom annotation
The following example could be done without the additional annotation as well, but since I assume you want to use your factory method with different values, you could declare a custom annotation (I named it RangeEnd here):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface RangeEnd {
int value();
}
Then given the following methods with annotating the parameter with @RangeEnd...
@ParameterizedTest
@ArgumentsSource(MyFactoryMethodProvider.class)
void testWithArgumentsSource(@RangeEnd(10) int argument) {
assertNotEquals(9, argument);
}
static IntStream myFactoryMethod(int end) {
return IntStream.range(0, end);
}
...the corresponding ArgumentsProvider implementation could look like this (using a nested static class):
static class MyFactoryMethodProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ParameterDeclarations parameters, ExtensionContext context) {
int value = parameters.getFirst() // assuming you only have one parameter
.map(param -> param.getAnnotatedElement().getAnnotation(RangeEnd.class))
.map(RangeEnd::value).orElseThrow(() -> new IllegalStateException("No @RangeEnd annotation found"));
return myFactoryMethod(value).mapToObj(Arguments::of);
+ i, i)));
}
}
Or a variation of the return statement returning named arguments:
return myFactoryMethod(value).mapToObj(i -> arguments(named("using value " + i, i)));
Conclusion: There are many different ways of achieving the same, the effort you want to put into it depends highly on how often you plan to reuse it.