jmockit / jmockit1

Advanced Java library for integration testing, mocking, faking, and code coverage
Other
458 stars 238 forks source link

Exception when configuring expectations on method returning enum with abstract methods on JDK17 #722

Open adessaigne opened 2 years ago

adessaigne commented 2 years ago

Hello,

In the following environment:

When testing the following code

public class ServiceTest {
    public enum Flag {
        ENABLED {
            @Override
            Flag flip() {
                return DISABLED;
            }
        },

        DISABLED {
            @Override
            Flag flip() {
                return ENABLED;
            }
        };

        abstract Flag flip();
    }

    public interface Service {
        Flag flag();
    }

    @Mocked
    Service m_service;

    @Test
    public void shouldWork() {
        new Expectations() {{
            m_service.flag();
            result = Flag.ENABLED;
        }};

        System.out.println(m_service.flag());
    }
}

We have the following exception

java.lang.IncompatibleClassChangeError: class demo.$Subclass_Flag_flag cannot inherit from sealed class demo.ServiceTest$Flag
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1012)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:874)
    at demo.ServiceTest$1.<init>(ServiceTest.java:38)
    at demo.ServiceTest.shouldWork(ServiceTest.java:37)

It fails because it tries to mock the enum. Is there a way to say "do not try to mock the result, I will provide it to you"? Thank you

adessaigne commented 2 years ago

Here is a dirty hack I've found that works around this issue

public static <T> void setJMockitDefaultValue(Class<T> aClass, T defaultValue) {
    requireNonNull(aClass, "The class must be defined");
    requireNonNull(defaultValue, "The default value must be defined");

    try {
        final Field field = DefaultValues.class.getDeclaredField("TYPE_DESC_TO_VALUE_MAP");
        field.setAccessible(true);
        final Map<String, Object> map = (Map<String, Object>) field.get(null);
        map.put(aClass.descriptorString(), defaultValue);
    } catch (Exception e) {
        throw new RuntimeException("Cannot initialize JMockit properly", e);
    }
}