DataProviderUtil.java

  1. package net.morimekta.testing.junit4;

  2. import java.util.ArrayList;
  3. import java.util.List;

  4. /**
  5.  * Utility for using <code>junit-dataprovider</code>.
  6.  */
  7. public class DataProviderUtil {
  8.     /**
  9.      * This is a utility to be used with <code>junit-dataprovider</code>. It will instead of taking in an array of
  10.      * argument arrays, will get an array of available dimensions, each with possible values. It will then multiply all
  11.      * the possible dimension possibilities with each other and result the array of parameter array that
  12.      * <code>junit-dataprovider</code> wants.
  13.      * <p>
  14.      * E.g. providing two dimensions, content type and locale, e.g. like this:
  15.      *
  16.      * <pre>{@code
  17.      * {@literal@}RunWith(DataProviderRunner.class)
  18.      * public class MyTest {
  19.      *     {@literal@}DataProvider
  20.      *     public static Object[][] testParams() {
  21.      *         return buildDataDimensions(
  22.      *             List.of(ContentType.WALLPAPER, ContentType.AUDIO),
  23.      *             List.of(Locale.US, Locale.DE, Locale.SIMPLIFIED_CHINESE));
  24.      *     }
  25.      *     {@literal@}Test
  26.      *     {@literal@}UseDataProvider("testParams")
  27.      *     public void testMyParams(ContentType contentType, Locale locale) {
  28.      *         // The test.
  29.      *     }
  30.      * }
  31.      * }</pre>
  32.      * <p>
  33.      * Will create 6 argument arrays, one for each combination of content type and locale as if the input was this:
  34.      *
  35.      * <pre>{@code
  36.      * return new Object[][]{
  37.      *     {ContentType.WALLPAPER, Locale.US},
  38.      *     {ContentType.WALLPAPER, Locale.DE},
  39.      *     {ContentType.WALLPAPER, Locale.SIMPLIFIED_CHINESE},
  40.      *     {ContentType.AUDIO, Locale.US},
  41.      *     {ContentType.AUDIO, Locale.DE},
  42.      *     {ContentType.AUDIO, Locale.SIMPLIFIED_CHINESE},
  43.      * });
  44.      * }</pre>
  45.      * <p>
  46.      * This can significantly shorten the argument list especially with larger set of dimensions and longer lists of
  47.      * options for each. It accepts null values and does not accept empty dimensions. Each dimension is called a 'layer'
  48.      * in the input.
  49.      *
  50.      * @param dimensions The available dimensions.
  51.      * @return The argument arrays.
  52.      */
  53.     public static Object[][] buildDataDimensions(List<?>... dimensions) {
  54.         if (dimensions.length == 0) {
  55.             throw new IllegalArgumentException("No dimensions provided");
  56.         }
  57.         if (dimensions[0].size() == 0) {
  58.             throw new IllegalArgumentException("Empty dimension in layer 1");
  59.         }

  60.         List<List<Object>> result = new ArrayList<>();
  61.         for (Object o : dimensions[0]) {
  62.             List<Object> base = new ArrayList<>();
  63.             base.add(o);
  64.             result.add(base);
  65.         }

  66.         for (int layer = 1; layer < dimensions.length; ++layer) {
  67.             if (dimensions[layer].size() == 0) {
  68.                 throw new IllegalArgumentException("Empty dimension in layer " + (layer + 1));
  69.             }

  70.             // The layer below is the one that will be multiplied with the
  71.             // arguments from this layer.
  72.             List<List<Object>> layerBelow = deepCopy(result);

  73.             Object first = dimensions[layer].get(0);
  74.             for (List<Object> l : result) {
  75.                 l.add(first);
  76.             }

  77.             for (int pos = 1; pos < dimensions[layer].size(); ++pos) {
  78.                 List<List<Object>> extraResults = deepCopy(layerBelow);
  79.                 for (List<Object> l : extraResults) {
  80.                     l.add(dimensions[layer].get(pos));
  81.                 }
  82.                 result.addAll(extraResults);
  83.             }
  84.         }

  85.         return makeDataParams(result);
  86.     }

  87.     /**
  88.      * Convenience method to be able to use lists instead of arrays to build the dimensions. This must be a a list of
  89.      * lists of equal sizes, with compatible argument objects in each list.
  90.      *
  91.      * @param lists List of list of arguments.
  92.      * @return Array of array of arguments.
  93.      */
  94.     public static Object[][] makeDataParams(List<List<Object>> lists) {
  95.         // assumes all inner lists are the same size.
  96.         Object[][] out = new Object[lists.size()][];
  97.         for (int i = 0; i < lists.size(); ++i) {
  98.             out[i] = lists.get(i).toArray(EMPTY);
  99.         }
  100.         return out;
  101.     }

  102.     private static List<List<Object>> deepCopy(List<List<Object>> base) {
  103.         List<List<Object>> copy = new ArrayList<>(base.size());
  104.         for (List<Object> list : base) {
  105.             copy.add(new ArrayList<>(list));
  106.         }
  107.         return copy;
  108.     }

  109.     private static final Object[] EMPTY = new Object[0];

  110.     private DataProviderUtil() {
  111.     }
  112. }