ParamsProviderUtil.java
package net.morimekta.testing.junit5;
import org.junit.jupiter.params.provider.Arguments;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* Utility class for building argument stream for {@link org.junit.jupiter.params.provider.MethodSource}.
*/
public final class ParamsProviderUtil {
/**
* This is a utility to be used with <code>junit-jupiter-params</code> argument expander. It will instead of just
* returning the argument stream provided, will take a set of arguments, each with a specific type, and return the
* cross product of these argument lists.
* <p>
* E.g. providing two dimensions, content type and locale, e.g. like this:
*
* <pre>{@code
* public class MyTest {
* public static Stream<Arguments> myParamsSource() {
* return buildArgumentDimensions(
* arguments(ContentType.WALLPAPER, ContentType.AUDIO),
* arguments(Locale.US, Locale.DE, Locale.SIMPLIFIED_CHINESE));
* }
* {@literal@}ParameterizedTest
* {@literal@}MethodSource("myParamsSource")
* public void testMyParams(ContentType contentType, Locale locale) {
* // The test.
* }
* }
* }</pre>
* <p>
* Will create 6 argument arrays, one for each combination of content type and locale as if the input was this:
*
* <pre>{@code
* return Stream.of(
* arguments(ContentType.WALLPAPER, Locale.US),
* arguments(ContentType.WALLPAPER, Locale.DE),
* arguments(ContentType.WALLPAPER, Locale.SIMPLIFIED_CHINESE),
* arguments(ContentType.AUDIO, Locale.US),
* arguments(ContentType.AUDIO, Locale.DE),
* arguments(ContentType.AUDIO, Locale.SIMPLIFIED_CHINESE),
* });
* }</pre>
* <p>
* This can significantly shorten the argument list especially with larger set of dimensions and longer lists of
* options for each. It accepts null values and does not accept empty dimensions. Each dimension is called a 'layer'
* in the input.
*
* @param dimensions The available dimensions.
* @return The argument arrays.
*/
public static Stream<Arguments> buildArgumentDimensions(Arguments... dimensions) {
if (dimensions.length == 0) {
throw new IllegalArgumentException("No dimensions provided");
}
if (dimensions[0].get().length == 0) {
throw new IllegalArgumentException("Empty dimension in layer 1");
}
List<List<Object>> result = new ArrayList<>();
for (Object o : dimensions[0].get()) {
List<Object> base = new ArrayList<>();
base.add(o);
result.add(base);
}
for (int layer = 1; layer < dimensions.length; ++layer) {
final int layerSize = dimensions[layer].get().length;
if (layerSize == 0) {
throw new IllegalArgumentException("Empty dimension in layer " + (layer + 1));
}
// The layer below is the one that will be multiplied with the
// arguments from this layer.
List<List<Object>> layerBelow = deepCopy(result);
Object first = dimensions[layer].get()[0];
for (List<Object> l : result) {
l.add(first);
}
for (int pos = 1; pos < layerSize; ++pos) {
List<List<Object>> extraResults = deepCopy(layerBelow);
for (List<Object> l : extraResults) {
l.add(dimensions[layer].get()[pos]);
}
result.addAll(extraResults);
}
}
return fromLists(result);
}
private static List<List<Object>> deepCopy(List<List<Object>> base) {
List<List<Object>> copy = new ArrayList<>(base.size());
for (List<Object> list : base) {
copy.add(new ArrayList<>(list));
}
return copy;
}
private static Stream<Arguments> fromLists(List<List<Object>> lists) {
// assumes all inner lists are the same size.
List<Arguments> out = new ArrayList<>(lists.size());
for (List<Object> list : lists) {
out.add(Arguments.arguments(list.toArray(EMPTY)));
}
return out.stream();
}
private static final Object[] EMPTY = new Object[0];
private ParamsProviderUtil() {
}
}