deep within JUnit Parameterized test runner: java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List

StackOverflow https://stackoverflow.com/questions/18754112

Question

I'm attempting to build my first parameterized test using JUnit's ParameterizedTestRunner. I am getting a weird ClassCastException deep in the bowels of JUnit which I cannot figure out.

Here's what's weird about this: the traceback in Intellij never seems to touch my code.

My base class is called MultiLocaleParserTest, and my child class is ExpectNoAltTagsInSellerNamesTest. Amazon scraping, rejoice!

/**
 * This is the base class for "multi-locale" tests.  Its children are intended
 * to be used with the JUnit Parameterized Test Runner.
 *
 * Currently, contains only boilerplate of use to child classes.
 *
 * @see <a href="https://github.com/junit-team/junit/wiki/Parameterized-tests">JUnit Parameterized Test Runner docs</a>
 */
public abstract class MultiLocaleParserTest extends BaseParserTest {

    private static ArrayList<ArrayList> fixtureList;
    private static ArrayList<ArrayList> expectedList;

    /**
     * Builds and returns an array that looks like this:
     *
     *  { { String amazonLocale, ArrayList fixtureData, ArrayList expectedData },
     *    { ... },
     *  }
     *
     * TODO: A possible (probable) refactor involves switching the "fixture" and
     *      "expected" data structures to Maps of some type, because it's more reader-
     *      friendly to see e.g. fixture.get("seller_name") than fixture.get(2).
     *
     * @param fixtureList An ArrayList of fixture data for the test the programmer is setting up.
     * @param expectedList An ArrayList of expected results for the test the programmer is setting up.
     * @return A data structure for use with parameterized tests as in class ExpectNoAltTagsInSellerNames.
     */
    protected static Object[] buildLocaleTestDataArray(ArrayList<ArrayList> fixtureList,
                                                       ArrayList<ArrayList> expectedList) throws Exception {
        MultiLocaleParserTest.fixtureList = fixtureList;
        MultiLocaleParserTest.expectedList = expectedList;
        if (fixtureList.size() != expectedList.size()) {
            throw new Exception("Need as many 'expected' entries as 'fixture' entries.");
        }

        int c = 0;
        ArrayList<Object[]> testParamData = new ArrayList<>();
        Iterator fixtureIterator = fixtureList.iterator();
        Iterator expectedIterator = expectedList.iterator();
        while (fixtureIterator.hasNext()) {
            ArrayList myFixtureList = (ArrayList)fixtureIterator.next();
            ArrayList myExpectedList = (ArrayList)expectedIterator.next();

            String fLocale = (String)myFixtureList.remove(0);
            String eLocale = (String)myExpectedList.remove(0);

            if (!fLocale.equals(eLocale)) {
                throw new Exception("List params to this method must be in the same order.");
            }

            ArrayList<Object> testParamList = new ArrayList<>();
            testParamList.add(fLocale);
            testParamList.add(myFixtureList);
            testParamList.add(myExpectedList);

            testParamData.add(testParamList.toArray());
            c++;
        }

        return testParamData.toArray();
    }

    public void smokeTest() throws Exception {
        // @todo derp
    }
}

And the child class, which attempts to actually use the base class. Sorry for all this, it's a mix of my not having touched Java in 6 years and having a Python mindset, and our existing codebase.

/**
 * Regression test vs MT-786 (alt="foo visible in MPS seller name fields)
 *
 * This multi-locale test checks that the string "alt=" does not appear in any
 * seller names which are images, rather than text.
 *
 * This is the proof-of-concept use of JUnit Parameterized tests for multiple
 * Amazon locales.
 *
 * @see MultiLocaleParserTest
 * @see <a href="https://github.com/junit-team/junit/wiki/Parameterized-tests">JUnit Parameterized Test Runner docs</a>
 */
@RunWith(Parameterized.class)
public class ExpectNoAltTagsInSellerNamesTest extends MultiLocaleParserTest {

    /**
     * Params that are filled for each localized test by the JUnit parameterized
     * test runner.  These go in order? or type/signature? based on the return
     * signature of data().
     */
    private String locale;
    private ArrayList fixtureData;
    private ArrayList expectedData;

    /**
     * Here is where you, the programmer, set up fixture data and expected results data
     * for each Amazon locale.  You then feed it to MultiLocaleParserTest#buildLocaleTestData
     * to transform it into a data structure for easy use by your actual test code.
     *
     * You must provide both fixture data and expected results for each test's Amazon locale
     * as an ArrayList with the Amazon locale as a String at element 0.
     *
     * Create an ArrayList of these locale-specific ArrayLists, as below, and feed it to
     * MultiLocalParserTest#buildLocaleTestDataArray() for your return value.
     *
     * The JUnit Parameterized Test Runner will then fill the private variables
     * #locale, #fixtureData, and #expectedData with the contents of each array
     * for every locale you define.
     *
     * NOTICE: JUnit 4.11+ should support an argument to the @Parameterized.Parameters()
     * annotiation which makes *which* test blew up when a particular test blows up more
     * understandable.  I was unable to get it to work, possibly due to old versions of
     * JUnit and/or Hamcrest Core.  The thing to paste in the parens is this:
     *
     *   name = "{index}: " + "expectProperHandlingOfImageSellerNames({0}) = {1}"
     *
     * cf. https://github.com/junit-team/junit/wiki/Parameterized-tests#identify-individual-test-cases
     * cf. http://youtrack.jetbrains.com/issue/IDEA-109891#comment=27-560090
     *
     * @return The value is described by MultiLocaleParserTest#buildLocaleTestDataArray()
     * @see MultiLocaleParserTest#buildLocaleTestDataArray(java.util.ArrayList, java.util.ArrayList)
     */
    @Parameterized.Parameters()
    public static Object[] data() throws Exception {
        ArrayList<ArrayList> fixtureList = new ArrayList<>();

        fixtureList.add(new ArrayList(Arrays.asList("UK", "/20130909-B0068ZAPB0-New--MST781.html")));
        fixtureList.add(new ArrayList(Arrays.asList("DE", "/20130909-B004LR5PRQ-New--MST781.html")));
        fixtureList.add(new ArrayList(Arrays.asList("FR", "/20130909-B001VL82J8-New--MST781.html")));

        ArrayList<ArrayList> expectedList = new ArrayList<>();
        expectedList.add(new ArrayList(Arrays.asList("UK", 10)));
        expectedList.add(new ArrayList(Arrays.asList("DE", 10)));
        expectedList.add(new ArrayList(Arrays.asList("FR", 10)));

        return buildLocaleTestDataArray(fixtureList, expectedList);
    }


    public void expectNoAltTagsInSellerNamesTest(String locale, ArrayList<Object> fixtureData, ArrayList<Object> expectedData) {
        this.locale = locale;
        this.fixtureData = fixtureData;
        this.expectedData = expectedData;
    }

    /**
     * Actual test code for this class.  Note the use of 'this.foo' variables,
     * which are stuffed by JUnit's Parameterized Test Runner before execution.
     *
     * There are a number of potential small refactors here.
     *
     * @throws Exception
     */
    @Test  // MST-781
    public void test() throws Exception {
        File htmlPage = named((String) this.fixtureData.get(0));
        assertThat(htmlPage, notNullValue());

        OfferList ol = getOfferList(htmlPage);
        assertThat(ol, notNullValue());

        int testVal = ol.getOffers().size();
        int expectVal = (Integer)this.expectedData.get(0);
        assertThat(testVal, is(expectVal));

        for (IOffer offer : ol.getOffers()) {
            String sellerName = offer.getSellerName();

            // look for seller names that have anything other than
            // \w (word chars), \s (whitespace), and [.,'"()&!-]
            // (in short, 'alt="Seller Name' should trigger this
            // @todo change to checking for "alt="
            Pattern pattern = Pattern.compile("[^\\w\\s.,'\"\\(\\)&!-]");
            Matcher matcher = pattern.matcher(sellerName);
//            System.out.println("Inspecting " + sellerName + ": " + matcher.find());
            assertThat(matcher.find(), is(false));
        }
    }

    public void smokeTest() throws Exception {
        // @todo derp
    }
}

Traceback:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.util.List
at org.junit.runners.Parameterized.getParametersList(Parameterized.java:149)
at org.junit.runners.Parameterized.<init>(Parameterized.java:135)
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.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:44)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63)
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)

Process finished with exit code 255

Was it helpful?

Solution

I assume that your're using JUnit 4.11.

The @Parameters method

You have to change two parts of your code. First the method data() has to return an Iterable<Object[]>, but you are returning an Object[]. This could be easy fixed by changing the last line of buildLocaleTestDataArray:

Is: return testParamData.toArray();
Should be: return testParamData;

and change the return types of the methods buildLocaleTestDataArray and data() to Iterable<Object[]>.

Assign the parameters

@Parameterized does not assign the parameters automatically. You can use the @Parameter(index) method

@Parameter(0)
public String locale;
@Parameter(1)
public ArrayList fixtureData;
@Parameter(2)
public ArrayList expectedData;

or change the method expectNoAltTagsInSellerNamesTest into a constructor

public ExpectNoAltTagsInSellerNamesTest(String locale, ArrayList<Object> fixtureData, ArrayList<Object> expectedData) {
    this.locale = locale;
    this.fixtureData = fixtureData;
    this.expectedData = expectedData;
}

OTHER TIPS

In the example given for JUnit (which your Javadoc links to), the method annotated @Parameters returns Collection<Object[]>, presumably where each Object[] is an array of arguments to be passed to the test case's constructor via Constructor.newInstance. Your return value from data() should be a Collection<Object[]> instead of just Object[]; I think you need to have an argument-taking constructor for ExpectNoAltTagsInSellerNamesTest, but the JUnit docs are lacking.

Little late but it can be useful. After the release JUnit 4.12 Beta 3, they have started supporting more return types like Iterator<? extends Object>, Object[] and Object[][]. Now no restriction is there to return Collection of Array. If you are using older version then just update it, and see it will work.

You can find more details on release notes of JUnit 4.12 Beta 3 under Parameterized Tests heading.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top