Search code examples
javacollectionsmultidimensional-arrayjunitparameterized

How can I return a multidimensional array as a collection of parameters for a JUnit test?


I'm fairly new to Java, and I'm certainly in over my head. Okay, disclaimer aside, here's my story:

I have a big list of video filenames and complementary strings called reference ID's in an XML file. The XML looks like this:

<testdata>
  <testcase displayName="video1" refId="vid1xxyyzz" />
  <testcase displayName="video2" refId="vid2aabbcc" />
  .
  .
  <testcase displayName="video499" refId="vid499ffoooo" />
</testdata>

I've converted the XML schema into a class using XMLBeans and I'm able to import the data into a couple of arrays using the method documented here: http://docs.oracle.com/javase/tutorial/reflect/special/arraySetGet.html

// File import:
    File refIdFile = new File("C:\testdata.xml");
    DisplayNameReferenceIdDoc = DisplayNameReferenceIdDocument.Factory.parse(refIdFile);
    displayNameReferenceId = DisplayNameReferenceIdDoc.getDisplayNameReferenceId();
    tests = displayNameReferenceId.getTestcaseArray();

// Multi-dimensional array get/set:
    matrix = Array.newInstance(String.class, 2, tests.length);
    Object row0 = Array.get(matrix, 0);
    Object row1 = Array.get(matrix, 1);
    for (int i = 0; i < tests.length; i++){
        displayName = tests[i].getDisplayName();
        refId = tests[i].getRefId();
        Array.set(row0, i, displayName);
        Array.set(row1, i, refId);
    }

That's all happening in a @Parameterized method in my test class, which must return a collection. Here's the whole thing, zoomed out:

@RunWith(Parameterized.class)
public class ValidateDisplayNameReferenceIdTest {

static DisplayNameReferenceIdDocument DisplayNameReferenceIdDoc;
static DisplayNameReferenceId displayNameReferenceId;
static DisplayNameReferenceId.Test[] tests;
static String displayName;
static String refId;
static Object matrix;

@Parameterized.Parameters(name="{index}: {0}")
public static Collection<Object> getTestParameters() throws IOException, XmlException {
    File refIdFile = new File("C:\testdata.xml");

    DisplayNameReferenceIdDoc = DisplayNameReferenceIdDocument.Factory.parse(refIdFile);
    displayNameReferenceId = DisplayNameReferenceIdDoc.getDisplayNameReferenceId();
    tests = displayNameReferenceId.getTestArray();
    matrix = Array.newInstance(String.class, 2, tests.length);
    Object row0 = Array.get(matrix, 0);
    Object row1 = Array.get(matrix, 1);
    for (int i = 0; i < tests.length; i++){
        displayName = tests[i].getDisplayName();
        refId = tests[i].getRefId();
        Array.set(row0, i, displayName);
        Array.set(row1, i, refId);
    }
    System.out.println("tweet");
    return Arrays.asList(matrix);  // NOT SURE ABOUT THIS!
}


private String displayNameInput;
private String refIdExpected;

public ValidateDisplayNameReferenceIdTest(String input, String expected ) {
    displayNameInput = input;
    refIdExpected = expected;
}

@Test
public void test() throws IOException {
    // send the API URL with 'displayNameInput', validate result for 'refIdExpected'
    URLConnection connection = new URL(url1 + displayNameInput + url2 + token).openConnection();
    connection.setRequestProperty("Accept-Charset", charset);
    InputStream response = connection.getInputStream();
    StringWriter writer = new StringWriter();
    IOUtils.copy(response, writer);
    String responseString = writer.toString();
    System.out.println(responseString);
    //Here goes something like assert(response.contains(refIdExpected));

    }
}

When I run this as is I get java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method).

I think I might be over-complicating the data structure, but now that it's built that way and seems to be successfully pulling in the data elements, I don't know how else to rebuild. Anyone see what I'm doing wrong here?

Here's the whole gory exception:

java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.junit.runners.Parameterized$TestClassRunnerForParameters.createTestUsingConstructorInjection(Parameterized.java:186)
at org.junit.runners.Parameterized$TestClassRunnerForParameters.createTest(Parameterized.java:181)
at org.junit.runners.BlockJUnit4ClassRunner$1.runReflectiveCall(BlockJUnit4ClassRunner.java:244)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:241)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runners.Suite.runChild(Suite.java:127)
at org.junit.runners.Suite.runChild(Suite.java:26)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:202)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:65)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Solution

  • Arrays.asList is defined like this:

    static <T> List<T> asList(T... a)
    

    It takes a variable-length parameter list. When you call a method like this, the program actually builds an array to pass to the method--unless you call it with one argument and the argument is already an array. In that case, it will pass the array you've already built.

    The problem here is that if you've defined matrix as an Object, the compiler won't see that as an array. So it constructs a one-element array with matrix as the element. Ultimately this messes up the JUnit code.

    To use asList, you'll need to make sure that the parameter has an actual array type (at compile time). However, I'm not sure just what will work. You want something like this:

    return Arrays.asList((Object[][])matrix);
    

    but I have a feeling the type cast will fail at run time. You may need to declare matrix as

    Object[][] matrix;
    

    and then instead of

    matrix = Array.newInstance(String.class, 2, tests.length);
    

    say

    matrix = new Object[2][tests.length];
    

    (I don't see any benefit to using Array.newInstance here anyway, instead of using just a regular new.)