Skip to content

Commit

Permalink
relax List dataprovider outer, inner, and inner inner types (#78)
Browse files Browse the repository at this point in the history
* now accepting anything derived from Iterable
* inner inner type is also relaxed to any arbitrary type, i.e. Iterable of anything
  • Loading branch information
aaschmid committed Oct 23, 2016
1 parent 010c602 commit 12e91d2
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,22 @@ public void testNumberFormat(Number number, String format, String expected) {
assertThat(result).isEqualTo(expected);
}

@DataProvider
public static List<? extends Number> dataProviderIsNumber() {
List<Number> result = new ArrayList<Number>();
result.add(101);
result.add(125L);
result.add(125.0);
return result;
}

@Test
@UseDataProvider("dataProviderIsNumber")
public void testIsNumber(Number number) {
// Expect:
assertThat(number).isInstanceOf(Number.class);
}

// @formatter:off
@Test
@DataProvider({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.tngtech.test.java.junit.dataprovider;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -41,4 +43,20 @@ public static List<Object> dataProviderSingleArgListOfObject() {
public void testSingleArgListOfObject(String string) {
// Check output within IDE
}

@DataProvider
public static Iterable<String> dataProviderSingleArgIterableOfString() {
Set<String> result = new HashSet<String>();
result.add(null);
result.add("");
result.add("1");
result.add("123");
return result;
}

@Test
@UseDataProvider
public void testSingleArgIterableOfString(String string) {
// Check output within IDE
}
}
41 changes: 19 additions & 22 deletions src/main/java/com/tngtech/java/junit/dataprovider/DataProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,34 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.List;

/**
* Mark a method as a dataprovider used by a test method or use it directly at the test method and provide data via
* {@link #value()} attribute.
* Mark a method as a dataprovider used by a test method or use it directly at the test method and provide data via {@link #value()}
* attribute.
* <ul>
* <li><i>Use it on a separate method:</i> The name of the dataprovider is the the name of the method. The method must
* be static and return an {@link Object}{@code [][]}, a {@link List}{@code <List<Object>>}, or a {@link String}
* {@code []}. The test method will be called with each "row" of this two-dimensional array. The test method must be
* annotated with {@code @}{@link UseDataProvider}. This annotation behaves pretty much the same as the
* {@code @DataProvider} annotation from <a href="http://testng.org/">TestNG</a>.
* <li><i>Use it on a separate method:</i> The name of the dataprovider is the the name of the method. The method must be static and return
* an {@link Object}{@code [][]}, an {@link Iterable}{@code <Iterable<?>>}, an {@link Iterable}{@code <?>}, or a {@link String}{@code []}.
* Whereby {@link Iterable} can be replaced with any vaild subtype as well as arbitrary inner types are also supported. The test method will
* be called with each "row" of this two-dimensional array. The test method must be annotated with {@code @}{@link UseDataProvider}. This
* annotation behaves pretty much the same as the {@code @DataProvider} annotation from <a href="http://testng.org/">TestNG</a>.
* <p>
* <b>Note:</b> The name of the test method in the JUnit result will by default be the name of the test method
* (annotated by {@code @}{@link UseDataProvider}) suffixed by the parameters, can be changed by customizing
* {@link #format()}.</li>
* <b>Note:</b> The name of the test method in the JUnit result will by default be the name of the test method (annotated by
* {@code @}{@link UseDataProvider}) suffixed by the parameters, can be changed by customizing {@link #format()}.</li>
* <li>
* <p>
* <i>Use it directly on test method:</i> Provide all the data for the test method parameters as regex-separated
* {@link String}s using {@code String[] value()}.
* <i>Use it directly on test method:</i> Provide all the data for the test method parameters as regex-separated {@link String}s using
* {@code String[] value()}.
* <p>
* <b>Note:</b> All parameters of the test method must be primitive types (e.g. {@code char}, {@code int},
* {@code double}), primitive wrapper types (e.g. {@link Boolean}, {@link Long}), case-sensitive {@link Enum} names,
* {@link String}s, or types having single-argument {@link String} constructor. The former two are converted using the
* {@code valueOf(String)} methods of their corresponding wrapper classes or
* {@code valueOf(Class<? extends Enum<?>>, String)}, respectively. This can cause {@link Exception}s at runtime. A
* {@link String} must not contain commas! The {@link String} "null" will be passed as {@code null} or {@link String},
* according to {@link #convertNulls()}.</li>
* <b>Note:</b> All parameters of the test method must be primitive types (e.g. {@code char}, {@code int}, {@code double}), primitive
* wrapper types (e.g. {@link Boolean}, {@link Long}), case-sensitive {@link Enum} names, {@link String}s, or types having single-argument
* {@link String} constructor. The former two are converted using the {@code valueOf(String)} methods of their corresponding wrapper classes
* or {@code valueOf(Class<? extends Enum<?>>, String)}, respectively. This can cause {@link Exception}s at runtime. A {@link String} must
* not contain commas! The {@link String} "null" will be passed as {@code null} or {@link String}, according to
* {@link #convertNulls()}.</li>
* </ul>
* <p>
* If the test method arguments are retrieved from a regex-separated {@link String}{@code []}, the additional annotation
* parameters can be used to customized the generation/conversion behavior.
* If the test method arguments are retrieved from a regex-separated {@link String}{@code []}, the additional annotation parameters can be
* used to customized the generation/conversion behavior.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ public static Object[][] testForEach(Object... args) {
* @param args which are wrapped in {@link Object} arrays and combined to {@link Object}{@code [][]}
* @return an array which contains {@link Object} arrays for each single element in the given {@link Iterable}
* @throws NullPointerException iif given {@code args} is {@code null}
* @deprecated since 1.12.0 {@link Iterable}{@code <?>} can directly be returned from any dataprovider method
*/
@Deprecated
public static <T> Object[][] testForEach(Iterable<T> args) {
if (args == null) {
throw new NullPointerException("args must not be null");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@ public DataConverter() {
}

/**
* Returns {@code true} iif this {@link DataConverter} can convert the given {@code type}. Currently supported
* {@code type}s:
* Returns {@code true} iif this {@link DataConverter} can convert the given {@code type}. Currently supported {@code type}s:
* <ul>
* <li>Object[][]</li>
* <li>List&lt;List&lt;Object&gt;&gt;</li>
* <li>List&lt;Object&gt;</li>
* <li>Iterable&lt;Iterable&lt;?&gt;&gt;</li>
* <li>Iterable&lt;?&gt;</li>
* <li>Object[]</li>
* <li>String[]</li>
* </ul>
*
* @param type to be checked for convertibility (use either {@link Method#getGenericReturnType()},
* {@link Method#getReturnType()}, or simple {@link Class} if possible)
* Please note, that {@link Iterable} can be replaced by any valid subtype (checked via {@link Class#isAssignableFrom(Class)}). As well
* as an arbitrary inner type is also accepted. Only rawtypes are not supported currently.
*
* @param type to be checked for convertibility (use either {@link Method#getGenericReturnType()}, {@link Method#getReturnType()}, or
* simple {@link Class} if possible)
* @return {@code true} iif given {@code type} can be converted.
*/
public boolean canConvert(Type type) {
Expand All @@ -48,9 +50,10 @@ public boolean canConvert(Type type) {

if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;

Type rawType = parameterizedType.getRawType();
if (List.class.isAssignableFrom((Class<?>) rawType)) {
return canConvertListOf(parameterizedType);
if (Iterable.class.isAssignableFrom((Class<?>) rawType)) {
return canConvertIterableOf(parameterizedType);
}
}
return false;
Expand Down Expand Up @@ -92,23 +95,24 @@ public List<Object[]> convert(Object data, boolean isVarArgs, Class<?>[] paramet
} else if (data instanceof Object[]) {
return convert((Object[]) data, isVarArgs, parameterTypes);

} else if (data instanceof List) {
} else if (data instanceof Iterable) {
@SuppressWarnings("rawtypes")
List listData = (List) data;
return convert(listData, isVarArgs, parameterTypes);
Iterable iterableData = (Iterable) data;
return convert(iterableData, isVarArgs, parameterTypes);

}
throw new ClassCastException(String.format(
"Cannot cast to either Object[][], Object[], String[], List<List<Object>>, or List<Object> because data was: %s", data));
throw new ClassCastException(
String.format("Cannot cast to either Object[][], Object[], String[], or Iterable because data was: %s", data));
}

private boolean canConvertListOf(ParameterizedType parameterizedType) {
private boolean canConvertIterableOf(ParameterizedType parameterizedType) {
if (parameterizedType.getActualTypeArguments().length == 1) {
Type innerType = parameterizedType.getActualTypeArguments()[0];
if (parameterizedType.getActualTypeArguments()[0] instanceof ParameterizedType) {
return List.class.isAssignableFrom((Class<?>) ((ParameterizedType) innerType).getRawType());
ParameterizedType innerType2 = (ParameterizedType) innerType;
return Iterable.class.isAssignableFrom((Class<?>) innerType2.getRawType());
}
return Object.class.equals(innerType);
return true;
}
return false;
}
Expand Down Expand Up @@ -138,13 +142,13 @@ private List<Object[]> convert(Object[] data, boolean isVarArgs, Class<?>[] para
return result;
}

private List<Object[]> convert(@SuppressWarnings("rawtypes") List data, boolean isVarArgs, Class<?>[] parameterTypes) {
private List<Object[]> convert(Iterable<?> data, boolean isVarArgs, Class<?>[] parameterTypes) {
List<Object[]> result = new ArrayList<Object[]>();
for (Object arguments : data) {
if (List.class.isInstance(arguments)) {
if (arguments != null && Iterable.class.isAssignableFrom(arguments.getClass())) {
@SuppressWarnings("rawtypes")
List list = (List) arguments;
result.add(objectArrayConverter.convert(list.toArray(), isVarArgs, parameterTypes));
Iterable iterable = (Iterable) arguments;
result.add(objectArrayConverter.convert(toArray(iterable), isVarArgs, parameterTypes));

} else {
result.add(singleArgConverter.convert(arguments, isVarArgs, parameterTypes));
Expand All @@ -153,6 +157,14 @@ private List<Object[]> convert(@SuppressWarnings("rawtypes") List data, boolean
return result;
}

private Object[] toArray(Iterable<?> iterable) {
List<Object> list = new ArrayList<Object>();
for (Object element : iterable) {
list.add(element);
}
return list.toArray();
}

public void setObjectArrayConverter(ObjectArrayConverter objectArrayConverter) {
this.objectArrayConverter = objectArrayConverter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public void validateDataProviderMethod(FrameworkMethod dataProviderMethod, DataP
}
if (!dataConverter.canConvert(method.getGenericReturnType())) {
errors.add(new Exception(messageBasePart
+ " either return Object[][], Object[], String[], List<List<Object>> or List<Object>"));
+ " either return Object[][], Object[], String[], Iterable<Iterable<?>>, or Iterable<?>, whereby any subtype of Iterable as well as an arbitrary inner type are also accepted"));
}
if (dataProvider.value().length > 0) {
errors.add(new Exception(messageBasePart + " not define @DataProvider.value()"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public abstract class AbstractObjectConverter<V> {
public abstract Object[] convert(V data, boolean isVarArgs, Class<?>[] parameterTypes);

/**
* Checks if the types of the given list of {@code arguments} matches the given test methods {@code parameterTypes}
* and throws an {@link Error} if not.
* Checks if the types of the given {@code arguments} matches the given test methods {@code parameterTypes} and throws an {@link Error}
* if not.
* <p>
* This method is package private (= visible) for testing.
* </p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import java.util.List;
import java.util.Set;

import com.tngtech.java.junit.dataprovider.internal.convert.SingleArgConverter;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
Expand All @@ -23,6 +21,7 @@
import com.tngtech.java.junit.dataprovider.BaseTest;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.internal.convert.ObjectArrayConverter;
import com.tngtech.java.junit.dataprovider.internal.convert.SingleArgConverter;
import com.tngtech.java.junit.dataprovider.internal.convert.StringConverter;

@RunWith(MockitoJUnitRunner.class)
Expand Down Expand Up @@ -101,27 +100,39 @@ public void testCanConvertShouldReturnTrueIfTypeIsListOfObject() {
}

@Test
public void testCanConvertShouldReturnFalseIfTypeIsIterableOfIterable() {
public void testCanConvertShouldReturnTrueIfTypeIsIterableOfQuestionMark() {
// Given:
Type type = getMethod("methodReturningIterableOfIterableOfObject").getGenericReturnType();
Type type = getMethod("methodReturningIterableOfQuestionMark").getGenericReturnType();

// When:
boolean result = underTest.canConvert(type);

// Then:
assertThat(result).isFalse();
assertThat(result).isTrue();
}

@Test
public void testCanConvertShouldReturnFalseIfTypeIsSetOfSet() {
public void testCanConvertShouldReturnTrueIfTypeIsIterableOfIterable() {
// Given:
Type type = getMethod("methodReturningSetOfSetOfObject").getGenericReturnType();
Type type = getMethod("methodReturningIterableOfIterableOfT").getGenericReturnType();

// When:
boolean result = underTest.canConvert(type);

// Then:
assertThat(result).isFalse();
assertThat(result).isTrue();
}

@Test
public void testCanConvertShouldReturnTrueIfTypeIsSetOfSet() {
// Given:
Type type = getMethod("methodReturningSetOfSetOfQuestionMark").getGenericReturnType();

// When:
boolean result = underTest.canConvert(type);

// Then:
assertThat(result).isTrue();
}

@Test
Expand Down Expand Up @@ -185,15 +196,15 @@ public void testCanConvertShouldReturnTrueIfTypeIsListListObject() {
}

@Test
public void testCanConvertShouldReturnFalseIfTypeIsListOfIterable() {
public void testCanConvertShouldReturnTrueIfTypeIsListOfIterable() {
// Given:
Type type = getMethod("methodReturningListOfIterableOfObject").getGenericReturnType();
Type type = getMethod("methodReturningListOfIterableOfNumber").getGenericReturnType();

// When:
boolean result = underTest.canConvert(type);

// Then:
assertThat(result).isFalse();
assertThat(result).isTrue();
}

@Test
Expand Down Expand Up @@ -443,15 +454,15 @@ public void testConvertCallStringConverterMultipleTimesForStringArrayWithMultipl

// -- methods used as Method objects -------------------------------------------------------------------------------

public static List<Iterable<Object>> methodReturningListOfIterableOfObject() {
public static List<Iterable<Number>> methodReturningListOfIterableOfNumber() {
return null;
}

public static Iterable<Iterable<Object>> methodReturningIterableOfIterableOfObject() {
public static <T extends String> Iterable<Iterable<T>> methodReturningIterableOfIterableOfT() {
return null;
}

public static Set<Set<Object>> methodReturningSetOfSetOfObject() {
public static Set<Set<?>> methodReturningSetOfSetOfQuestionMark() {
return null;
}

Expand Down Expand Up @@ -484,6 +495,10 @@ public static List<Object> methodReturningListOfObject() {
return null;
}

public static List<?> methodReturningIterableOfQuestionMark() {
return null;
}

@SuppressWarnings("serial")
private static class SubList<A> extends ArrayList<A> {
// not required for now :-)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ public void testValidateDataProviderMethodShouldAddErrorIfDataProviderMethodRetu
// Then:
assertThat(errors).hasSize(1);
assertThat(errors.get(0).getMessage()).contains(dataProviderName).containsIgnoringCase(
"must either return Object[][], Object[], String[], List<List<Object>> or List<Object>");
"must either return Object[][], Object[], String[], Iterable<Iterable<?>>, or Iterable<?>");
}

@Test
Expand Down Expand Up @@ -403,7 +403,7 @@ public void testValidateDataProviderMethodShouldAddErrorsIfDataProviderMethodIsC
assertThat(errors.get(2).getMessage()).contains(dataProviderName).containsIgnoringCase(
"must either have a single FrameworkMethod parameter or none");
assertThat(errors.get(3).getMessage()).contains(dataProviderName).containsIgnoringCase(
"must either return Object[][], Object[], String[], List<List<Object>> or List<Object>");
"must either return Object[][], Object[], String[], Iterable<Iterable<?>>, or Iterable<?>");
assertThat(errors.get(4).getMessage()).contains(dataProviderName).containsIgnoringCase(
"must not define @DataProvider.value()");
}
Expand Down

0 comments on commit 12e91d2

Please sign in to comment.