ClassCastException and NoSuchMethodException with sophisticated generic class hierarchy

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

  •  14-07-2023
  •  | 
  •  

Domanda

The following test class contains a simplified version of my project's complicated class hierarchy which is based on generics. All 3 tests, genericsFailure_NoSuchMethod(), genericsFailure_ClassCast1(), genericsFailure_ClassCast2() pass fine with JMockit 1.6, but as of JMockit 1.7 and 1.8 I keep getting different sorts of ClassCastException and NoSuchMethodException from within JMockit.

package tests;

import mockit.Cascading;
import mockit.Mocked;
import mockit.NonStrictExpectations;

import org.testng.annotations.Test;

public class JMockitTests
{
    public static interface IAppBound<APP extends IApp<?, ?>>
    {
        APP getApp();
    }

    public static interface IBase<APP extends IApp<APP, ?>> extends IAppBound<APP>
    {

    }

    public static interface IApp<APP extends IApp<APP, ?>, BACK extends IBack<APP>> extends IBase<APP>
    {
        BACK getBack();
    }

    public static interface IBack<APP extends IApp<APP, ?>> extends IBase<APP>
    {

    }

    public static abstract class BaseImpl<APP extends AppImpl<APP, ?>> implements IBase<APP>
    {
        private APP app;

        @Override
        public APP getApp()
        {
            return app;
        }
    }

    public static class AppImpl<APP extends AppImpl<APP, ?>, BACK extends BackImpl<APP>> extends BaseImpl<APP> implements IApp<APP, BACK>
    {
        private BACK    b;

        @Override
        public BACK getBack()
        {
            return b;
        }
    }

    public static class BackImpl<APP extends AppImpl<APP, ?>> extends BaseImpl<APP> implements IBack<APP>
    {

        private Registry<APP, BackImpl<APP>>    reg;

        public Registry<APP, BackImpl<APP>> getRegistry()
        {
            return reg;
        }

        @Override
        public APP getApp()
        {
            return super.getApp();
        }
    }

    public static class Registry<APP extends AppImpl<APP, ?>, BACK extends BackImpl<APP>>
    {
        public BACK getBack(APP app)
        {
            return null;
        }

        public BACK getBack(BACK app)
        {
            return null;
        }

        public BACK getBack(String app)
        {
            return null;
        }
    }

    public static class SpecificApp extends AppImpl<SpecificApp, BackImpl<SpecificApp>>
    {

    }

    @Test
    public void genericsFailure_NoSuchMethod(@Mocked @Cascading AppImpl<?, ?> app, @Mocked @Cascading IBack<?> back)
    {
        new NonStrictExpectations()
        {
            {
                back.getApp();
                result = app;
            }
        };
    }

    @Test
    public void genericsFailure_ClassCast1(@Mocked @Cascading BackImpl<SpecificApp> back)
    {
        SpecificApp app = new SpecificApp();

        new NonStrictExpectations()
        {
            {
                back.getRegistry().getBack(app);
                result = back;
            }
        };
    }

    @Test
    public void genericsFailure_ClassCast2(@Mocked @Cascading BackImpl<SpecificApp> back)
    {
        SpecificApp app = new SpecificApp();

        new NonStrictExpectations()
        {
            {
                back.getApp();
                result = app;
            }
        };
    }
}

I think either something has changed within JMockit's handling of generics which is causing this, or I'm totally missing something about the fundamental concepts of JMockit. To be sure, I consulted both version history and documentation once more, but nothing caught my eye pointing to this.

I must mention that I've tested this with both JDK8 and JDK7 and still the problem exists. I'm running TestNG (6.8.6) as an eclipse plugin.

Here are the test results by TestNG:

FAILED: genericsFailure_ClassCast1(tests.JMockitTests$BackImpl@883ce7)
java.lang.ClassCastException: java.lang.Object cannot be cast to tests.JMockitTests$BackImpl
    at tests.JMockitTests$Registry.getBack(JMockitTests.java)
    at tests.JMockitTests$2.<init>(JMockitTests.java:113)
    at tests.JMockitTests.genericsFailure_ClassCast1(JMockitTests.java:110)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:483)

FAILED: genericsFailure_ClassCast2(tests.JMockitTests$BackImpl@a7e666)
java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class
    at tests.JMockitTests$BackImpl.getApp(JMockitTests.java)
    at tests.JMockitTests$3.<init>(JMockitTests.java:127)
    at tests.JMockitTests.genericsFailure_ClassCast2(JMockitTests.java:124)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:483)

FAILED: genericsFailure_NoSuchMethod(tests.JMockitTests$AppImpl@4f6be2, tests.$Impl_IBack@aac163)
java.lang.RuntimeException: java.lang.NoSuchMethodException: tests.JMockitTests$IBack.getApp()
    at tests.JMockitTests$1.<init>(JMockitTests.java:99)
    at tests.JMockitTests.genericsFailure_NoSuchMethod(JMockitTests.java:96)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:483)
Caused by: java.lang.NoSuchMethodException: tests.JMockitTests$IBack.getApp()
    at java.lang.Class.getDeclaredMethod(Class.java:2117)
    ... 4 more

I would be thankful if someone could approve this as an issue or could point me towards the solution.

EDIT: I've opened an issue (issue 350) on the project for tracking this further.

È stato utile?

Soluzione

I am surprised that such tests worked in JMockit 1.6, since it didn't have much in the way of explicit support for generics during cascaded mocking.

Anyway, the changes since 1.7 were related to issue 333, which is still open. Both issues will be solved for JMockit 1.9, though.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top