Reputation: 597
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.
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);
}
Upvotes: 7
Views: 7447
Reputation: 406
I like @Miguel Alorda's solution of the wrapper function, however what also works is casting to the right type:
Arguments.of((Function<String,Integer>) String::length)
Or with an inline lambda such as this no-argument lambda:
Arguments.of((Supplier<String>) ()-> "Supply World")
BTW: You don't actually need the return-type, but you do need the type of the operand of the Function<T,R>
otherwise the compiler cannot determine th method-reference to the length()
function.
So the following compiles:
Arguments.of((Function<String,?>) String::length)
But this doesn't:
Arguments.of((Function<?,?>) String::length)
Upvotes: 2
Reputation: 692
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)));
}
Upvotes: 2
Reputation: 394
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.
Upvotes: 1
Reputation: 597
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));
}
Upvotes: -1