6

Consider this test class, working with JUnit 4 and JUnitParams:

import static junitparams.JUnitParamsRunner.$;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;

import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)
public class JUnitParamsExample {

    private int[] getIntArray() {
        int array[] = new int[2];
        array[0] = 1;
        array[1] = 2;
        return array;
    }

    public Object getInts() {
        return $($(getIntArray()));
    }

    @Parameters(method = "getInts")
    @Test
    public void testIntArray(int... values) {
        //
    }

    private String[] getStringArray() {
        String array[] = new String[2];
        array[0] = "a";
        array[1] = "b";
        return array;
    }

    public Object getStrings() {
        return $($(getStringArray()));
    }

    @Parameters(method = "getStrings")
    @Test
    public void testStringArray(String... values) {
        //
    }
}

The test method testIntArray runs fine, while testStringArray does not. The error output is:

java.lang.IllegalArgumentException: Cannot parse parameters. Did you use , as column separator? a
    at junitparams.internal.InvokeParameterisedMethod.castParamsFromString(InvokeParameterisedMethod.java:51)
    at junitparams.internal.InvokeParameterisedMethod.<init>(InvokeParameterisedMethod.java:35)
    at junitparams.internal.ParameterisedTestClassRunner.buildMethodInvoker(ParameterisedTestClassRunner.java:121)
    at junitparams.internal.ParameterisedTestClassRunner.parameterisedMethodInvoker(ParameterisedTestClassRunner.java:115)
    at junitparams.JUnitParamsRunner.methodInvoker(JUnitParamsRunner.java:425)
    at org.junit.runners.BlockJUnit4ClassRunner.methodBlock(BlockJUnit4ClassRunner.java:251)
    at junitparams.JUnitParamsRunner.runChild(JUnitParamsRunner.java:405)
    at junitparams.JUnitParamsRunner.runChild(JUnitParamsRunner.java:383)
    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.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.IllegalArgumentException: Parameter type cannot be handled! Only primitive types and Strings can be used.
    at junitparams.internal.InvokeParameterisedMethod.castParameterDirectly(InvokeParameterisedMethod.java:171)
    at junitparams.internal.InvokeParameterisedMethod.castAllParametersToProperTypes(InvokeParameterisedMethod.java:122)
    at junitparams.internal.InvokeParameterisedMethod.castParamsUsingConverters(InvokeParameterisedMethod.java:101)
    at junitparams.internal.InvokeParameterisedMethod.castParamsFromString(InvokeParameterisedMethod.java:49)

I'm wondering why and int array works and a string array does not. Is there a way to pass a string array to a varargs test method with junitparams?

EDIT: What I would like to write is something like this:

public Object getStrings() {
        return $($("Hello", "world"),
                $("Me", "You"),
                $("Dog", "Cat"));
}

such that all inner $-entries are passed to the varargs argument.

Adam Michalik
  • 9,678
  • 13
  • 71
  • 102
Juergen
  • 3,489
  • 6
  • 35
  • 59

2 Answers2

5

Okay, basically it looks like it's a bug in JUnitParams. After applying the fix in the original answer below, the library still "unwraps" the String[] when we don't want it to - given the amount of conditional wrapping and unwrapping going on in the source, it seems that either the author is trying to be too clever for their own good, or they don't really know what they're expecting at any one time. The difference occurs in safelyCastParamsToArray, where in the int[] case it's passed an int[] and wraps it in an Object[], whereas in the String[] cases it's passed a String[] which is not wrapped because it can be cast to Object[] already.

You can make it work by making your method return a doubly-wrapped array, like this:

return $((Object)$((Object)getStringArray()));

Or just:

return new Object[] { new Object[] { getStringArray() } };

The int[] version only needs a single level of wrapping however, so you can just use:

return $(getIntArray());

rather than the "attempted double wrapping which is foiled by varargs" in your current code.

Personally I'm somewhat wary of all of this - it has a distinctly "black magic, tinker until it works" feeling which doesn't tend to be present in clean APIs...


Original answer

I believe the problem is that a String[] array is already an Object[], so you're ending up with it not being wrapped in the way that you want it to - varargs is treating the argument as as the whole array rather than one element of the array. You can fix this fairly easily though:

public Object getStrings() {
    Object array = getStringArray();
    return $(array);
}

Or just use a cast:

public Object getStrings() {
    return $((Object) getStringArray());
}

Or just ignore the $ method in this case:

public Object getStrings() {
    return new Object[] { getStringArray() };
}

(I very much doubt that you need $($(...)) in either case - you're just trying to make an array of arrays, right? So only a single level of wrapping is required, and you're only getting a single level of wrapping anyway, for precisely the same reason that your original code didn't work with the string array.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • The varargs should treat the argument as the whole array, because the actual method under test uses also a varargs argument. I just want to pass this array to the test method. With your suggestion JUnit gives this error: java.lang.IllegalStateException: While trying to create object of class class [Ljava.lang.String; could not find constructor with arguments matching (type-wise) the ones given in parameters. – Juergen Feb 05 '14 at 16:34
  • @Juergen: The fact that it's a varargs parameter in the test method is almost certainly unnoticed to the test runner - it just thinks of it as a string array, which I think it's having trouble creating... – Jon Skeet Feb 05 '14 at 16:40
  • I can get it work with passing a string list to the test method and then converting it to an array which in turn is passed to the method under test. But it looks ugly and "overheaded"... – Juergen Feb 05 '14 at 16:44
  • @Juergen: Sure - but do you understand that in your original code, you've effectively got `return getStringArray();`? The `$(...)` calls are doing nothing. I'm going to check out the JUnitParams code and try to debug through it - it's possible this is just a restriction of the library. – Jon Skeet Feb 05 '14 at 16:56
  • Thanks for your effort! I also looked at safelyCastParamsToArray but came to no conclusion. Maybe I create a ticket :-) – Juergen Feb 05 '14 at 17:36
  • Since I ran into the same issue with JUnitParams 1.0.4, I created [issue #68](https://github.com/Pragmatists/JUnitParams/issues/68) – Adam Michalik Dec 03 '15 at 13:56
-1

maybe zohhak library can help you. it's based on junit params. it allows you to only provide parameters in annotation as a string, but it allows you to easily create your own custom parsers for those strings

piotrek
  • 13,982
  • 13
  • 79
  • 165