11

I'm looking for how to pass a parameter into a metod that use the @MethodSource annotatio in Junit5

For example I need to invoke @MethodSource annotation and passing a value into the method "MyFactoryMethod". It is possible? Sometings like this:

@ParameterizedTest
@MethodSource("MyFactoryMethod(10)")
void testWithMethodSource(int argument) {
    assertNotEquals(9, argument);
}

static IntStream MyFactoryMethod(String var) {
    return IntStream.range(0, var);
}

Thanks in advance

3 Answers 3

10

Unfortunately, it's not supported, at least in Jupiter JUnit v.5.8.2.

You are allowed to provide names of methods within the test class as well as external classes. Note that methods in external classes must be referenced by fully qualified method name - com.package.Class#methodName.

Example:

Referring to a method within the test class @MethodSource("getParams")

External methods @MethodSource("com.app.test.ArgumentsProvider#getArgs")

There is an option to implement a custom annotation of your own, if nothing else suits you maybe consider that.

Example of how to implement such annotation

Sign up to request clarification or add additional context in comments.

Comments

2

There is a simple workaround, just use method override:

@ParameterizedTest
@MethodSource("myFactoryMethod10")
void testWithMethodSource(int argument) {
    assertNotEquals(10, argument);
}

@ParameterizedTest
@MethodSource("myFactoryMethod9")
void testWithMethodSource(int argument) {
    assertNotEquals(9, argument);
}

public static IntStream myFactoryMethod10() {
    return factoryMethod(10);
}

public static IntStream myFactoryMethod9() {
    return factoryMethod(9);
}

static IntStream factoryMethod(String var) {
    return IntStream.range(0, var);
}

You can then create versions of myFactoryMethod as required for individual tests. An extra step, but workable - huh?

The above solution may appear as an overkill for the above simple case, but imagine your factoryMethod is complex and creating many parameters, then the above method pays off.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.