junit-team / junit5

✅ The 5th major version of the programmer-friendly testing framework for Java and the JVM

Home Page:https://junit.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`ParameterResolutionException` for `Stream` of one-dimensional object array

augustindelecluse opened this issue · comments

Currently working with Junit 5, version 5.10.2. When working with parametrized tests and streams, the conversion between one object (say Integer) into an array of objects (say Integer[]) does not seem to work well. Instead of getting an array of objects, the value given as input is the first element of the array.

Steps to reproduce

public class ParameterIssueTests {

    /**
     * @return stream of array's containing 2 elements
     */
    public static Stream<Integer[]> ArrayStreamKO() {
        return IntStream.range(0, 2).mapToObj(i -> new Integer[] {i, i});
    }

    @ParameterizedTest
    @MethodSource("ArrayStreamKO")
    public void Bar(Integer[] array) {
        // fails with the message
        // org.junit.jupiter.api.extension.ParameterResolutionException: Error converting parameter at index 0:
        // No built-in converter for source type java.lang.Integer and target type java.lang.Integer[]
    }

    @ParameterizedTest
    @MethodSource("ArrayStreamKO")
    public void Foo(Object o) {
        // prints
        // 0
        // 1
        // instead of [0, 0] and [1, 1]
        System.out.println(o);
    }
    @Test
    public void Baz() {
        // prints
        // [0, 0]
        // [1, 1]
        List<Integer[]> valueList = ArrayStreamKO().toList();
        for (Integer[] array: valueList)
            System.out.println(Arrays.toString(array));
    }
    
}

Note that converting to Arguments and a casting seems to fix the problem. Replacing the ArrayStreamKO method by the following works, but requires the casting to Object:

    public static Stream<Arguments> ArrayStreamOK() {
        return IntStream.range(0, 2).mapToObj(i -> Arguments.of((Object) new Integer[] {i, i}));
    }

Without the casting to Object, the same error as above arises.

Context

  • Used versions (Jupiter/Vintage/Platform): Jupiter 5.10.2
  • Build Tool/IDE: maven and IntelliJ

This is the expected behavior.

The Javadoc for @MethodSource explicitly states:

Please note that a one-dimensional array of objects supplied as a set of "arguments" will be handled differently than other types of arguments. Specifically, all of the elements of a one-dimensional array of objects will be passed as individual physical arguments to the @ParameterizedTest method. This behavior can be seen in the table below for the static Stream<Object[]> factory() method: the @ParameterizedTest method accepts individual String and int arguments rather than a single Object[] array. In contrast, any multidimensional array supplied as a set of "arguments" will be passed as a single physical argument to the @ParameterizedTest method without modification. This behavior can be seen in the table below for the static Stream<int[][]> factory() and static Stream<Object[][]> factory() methods: the @ParameterizedTest methods for those factories accept individual int[][] and Object[][] arguments, respectively.

See also #1665.

You can use a custom ArgumentsAggregator to achieve your goal as follows.

@ParameterizedTest
@MethodSource("arguments")
void test(@AggregateWith(IntegerArrayAggregator.class) Integer[] array) {
	System.err.println(Arrays.toString(array));
}

static Stream<Integer[]> arguments() {
	return IntStream.range(0, 2).mapToObj(i -> new Integer[] { i, i });
}

static class IntegerArrayAggregator implements ArgumentsAggregator {

	@Override
	public Integer[] aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {
		return arguments.toList().stream().map(Integer.class::cast).toArray(Integer[]::new);
	}
}

Running that prints:

[0, 0]
[1, 1]

The example I provided above allows you to keep the Stream<Integer[]> return type for your factory method.

However, as you mentioned, the other option is to change the return type to Stream<Arguments> and cast the array to an Object and wrap that in an Arguments instance, as you did in:

    public static Stream<Arguments> ArrayStreamOK() {
        return IntStream.range(0, 2).mapToObj(i -> Arguments.of((Object) new Integer[] {i, i}));
    }

@augustindelecluse, do you have a specific requirement to use Integer[]?

If not, switching to a primitive integer array (int[]) is actually the easiest solution.

@ParameterizedTest
@MethodSource("ints")
void test(int[] array) {
	System.out.println(Arrays.toString(array));
}

static Stream<int[]> ints() {
	return IntStream.range(0, 2).mapToObj(i -> new int[] { i, i });
}

Running that also prints:

[0, 0]
[1, 1]