JUnit 5 and Arguments.of() with functions

Viewed 1770

Writing a JUnit 5 parameterized test and need to pass functions to the test using Arguments.of(), but there are 2 compile errors that I don't know how to fix. Any help would be appreciated.

  1. The method of(Object...) in the type Arguments is not applicable for the arguments (boolean, String::length)
  2. The target type of this expression must be a functional interface

public static Stream<Arguments> some() {
    return Stream.of(Arguments.of(true, String::length));
}

@ParameterizedTest
@MethodSource
public <T> void some(final T input, final Function<String, Integer> length) {
}

The following works as expected.

public void sample() {
    some(true, String::length);
}
3 Answers

Wrap the arguments in a helper method
Similar to the answer "wrap it in a class", but possibly less intrusive, is to use a helper method to pass the functional interface as a java.lang.Object.

For example, the first raw method reference, Math::ciel, in this parameterized test:

@ParameterizedTest
@MethodSource("testCases")
void shouldExerciseMethod(Function<Double, Double> method, Double expected) {
    assertEquals(expected, method.apply(1.5d), 1.0E-8d);
}
    
static Stream<Arguments> testCases() {
    return Stream.of(Arguments.of(Math::ceil, 2.0d),
                     Arguments.of(Math::floor, 1.0d));
}

causes this compilation error:

java: method of in interface org.junit.jupiter.params.provider.Arguments cannot be applied to given types;
  required: java.lang.Object[]
  found: Math::ceil,double
  reason: varargs mismatch; java.lang.Object is not a functional interface

which you can get around by passing the arguments through a helper method:

static <T, U> Arguments args(Function<T, U> method, U expected) {
    return Arguments.of(method, expected);
}

so:

static Stream<Arguments> testCases() {
    return Stream.of(args(Math::ceil, 2.0d), 
                     args(Math::floor, 1.0d));
}

My attempts to make the idiom more general using varargs failed with variations on the same error, so I have ended up overloading it whenever I need another signature.

The function needs to be wrapped in a class.

public static class P {

    private final Function<String, Integer> mFunction;

    public P(final Function<String, Integer> function) {
        mFunction = function;
    }

    public Function<String, Integer> function() {
        return mFunction;
    }
}

public static Stream<Arguments> some() {
    return Stream.of(Arguments.of(3, "abc", new P(String::length)));
}

@ParameterizedTest
@MethodSource
public <T> void some(final int expect, final String input, final P p) {
    assertEquals(expect, p.function().apply(input));
}

I liked @adrian-redgers solution, but I think overloading a method for each signature needed is a bit overkill.

You only really need to convert the functional interface to an object. So the solution I implemented was:

/**
 * Helps to use {@link org.junit.jupiter.params.provider.Arguments#of(Object...)}, as functional
 * interfaces cannot be converted into an object directly.
 */
public class ArgumentsWrapper {

    private ArgumentsWrapper() {
        throw new IllegalStateException(
                ArgumentsWrapper.class + " util class cannot be instantiated");
    }

    public static <T, U> Function<T, U> wrap(Function<T, U> function) {
        return function;
    }
}

Then, it can be used as:

 public static Stream<Arguments> testMapAlarmTypeConfigWithLanguage() {
    return Stream.of(
        // Statically imported ArgumentsWrapper#wrap
        Arguments.of(null, wrap(AlarmTypeConfig::getNameInEnglish)),
        Arguments.of("en-us", wrap(AlarmTypeConfig::getNameInEnglish)),
        Arguments.of("es-es", wrap(AlarmTypeConfig::getNameInSpanish)));
}
Related