New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deep Stubs Incompatible With Mocking Enum #2984
Comments
The working code fails if |
@SenorPez Can you please provide a full running sample including the referenced classes and enums. My Test class is working as expected: public class DeepStubReturnsEnumTest {
@Test
public void deep_stub_can_mock_enum_Issue_2984() {
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
when(mock.getTestEnum()).thenReturn(TestEnum.B);
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B);
}
@Test
public void deep_stub_can_mock_enum_method_Issue_2984() {
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
assertThat(mock.getTestEnum().getDoubleValue()).isEqualTo(0.0);
when(mock.getTestEnum().getDoubleValue()).thenReturn(1.0);
assertThat(mock.getTestEnum().getDoubleValue()).isEqualTo(1.0);
}
@Test
public void mock_mocking_enum_Issue_2984() {
final var mock = mock(TestClass.class);
when(mock.getTestEnum()).thenReturn(TestEnum.B);
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B);
assertThat(mock.getTestEnum().getDoubleValue()).isEqualTo(2.0);
}
private static class TestClass {
TestEnum getTestEnum() {
return TestEnum.A;
}
}
private enum TestEnum {
A,
B;
double getDoubleValue() {
return 2.0;
}
}
} |
I'm facing the same problem and happened to replicate the error. @RunWith(MockitoJUnitRunner.class)
public class FooTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Clazz clazz;
@Test
public void test() {
Enumeration meme = clazz.getEnumeration();
}
class Clazz {
private final Enumeration enumeration;
public Clazz(Enumeration enumeration) {
this.enumeration = enumeration;
}
public Enumeration getEnumeration() {
return enumeration;
}
}
enum Enumeration {
FIRST {
@Override
void doSomething() {
System.out.println(this.name());
}
},
SECOND {
@Override
void doSomething() {
System.out.println(this.name());
}
};
abstract void doSomething();
}
} Edited a few times to replicate it :x |
@danielqejo On which version do you execute that? Mockito, ByteBuddy, Java, etc. So I can't reproduce your problem. I am executing: package org.mockito.internal.stubbing.answers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class FooTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Clazz clazz;
@Test
public void test() {
Enumeration meme = clazz.getEnumeration();
}
class Clazz {
private final Enumeration enumeration;
public Clazz(Enumeration enumeration) {
this.enumeration = enumeration;
}
public Enumeration getEnumeration() {
return enumeration;
}
}
enum Enumeration {
FIRST {
@Override
void doSomething() {
System.out.println(this.name());
}
},
SECOND {
@Override
void doSomething() {
System.out.println(this.name());
}
};
abstract void doSomething();
}
} |
I'm using:
|
Here's complete stacktrace: org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class <hidden_package>.FooTest$Enumeration.
If you're not sure why you're getting this error, please open an issue on GitHub.
Java : 17
JVM vendor name : Amazon.com Inc.
JVM vendor version : 17.0.8.1+8-LTS
JVM name : OpenJDK 64-Bit Server VM
JVM version : 17.0.8.1+8-LTS
JVM info : mixed mode, sharing
OS name : Linux
OS version : 5.15.0-86-generic
You are seeing this disclaimer because Mockito is configured to create inlined mocks.
You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.
Underlying exception : org.mockito.exceptions.base.MockitoException: Unsupported settings with this type '<hidden_package>.FooTest$Enumeration'
at <hidden_package>.FooTest$Clazz.getEnumeration(FooTest.java:28)
at <hidden_package>.FooTest.test(FooTest.java:17)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:55)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:100)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:107)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:42)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: org.mockito.exceptions.base.MockitoException: Unsupported settings with this type '<hidden_package>.FooTest$Enumeration'
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:168)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:399)
at net.bytebuddy.TypeCache.findOrInsert(TypeCache.java:190)
at net.bytebuddy.TypeCache$WithInlineExpunction.findOrInsert(TypeCache.java:410)
... 31 more |
I've also tested with JUnit 5, but no luck whatsoever. |
I've been doing some tests and found out that the problem occurs at Java 17+ only. |
Turns out Java 17 is making enum sealed when there is an abstract method. |
@danielqejo Thanks for the great analysis. But I have not yet a clue how to fix it. The main point in the reproducer is to compile the Enum and run tests with Java 17. |
@TimvdLippe I don't think we can fix that problem here, maybe we should add a better error message that mocking of Enums since Java 15 (see sealed-classes) does not work. The problem is, that enums compiled with Java >= 15 (I guess due to preview feature, but at least Java 17) are sealed and have the Our tests are currently not failing, because we compile our test enums with Java 11. |
If this indeed does not work (@raphw can you confirm?) then indeed let's try to detect those cases when the mockmaker is invoked whether it can feasibly mock them. I don't think we should hack our way around it. Sealed is there for a purpose, so let's respect it. |
@TimvdLippe In addition to allow the deep stub of a class returning an enum working again in the Java 17 case, we could change the deep stup code to return the first literal of the enum, if we can't mock the enum. |
Yes, sealed classes cannot be subclassed, also with an agent, and the attribute cannot be modified. I also think that we should avoid mocking enums for this reason. Possibly, we can add a mechanism along the static method mocking facility that treats enums similarly. |
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. If a deep stub returns an abstract enum, it uses in the error case now the first enum literal of the real enum. Fixes mockito#2984
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. If a deep stub returns an abstract enum, it uses in the error case now the first enum literal of the real enum. Fixes mockito#2984
Just to be clear for the future. Mockito can still mock non-abstract enums in Java 17. It only can't mock abstract enums with abstract methods, which are overridden by literals. |
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. If a deep stub returns an abstract enum, it uses in the error case now the first enum literal of the real enum. Fixes mockito#2984
True. We'd need to navigate and mock any of the extended classes in such a case, that would be a close enough emulation. |
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. If a deep stub returns an abstract enum, it uses in the error case now the first enum literal of the real enum. Fixes mockito#2984
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. If a deep stub returns an abstract enum, it uses in the error case now the first enum literal of the real enum. Added spotless.gradle to also check formatting of java21 project. Fixes mockito#2984
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. If a deep stub returns an abstract enum, it uses in the error case now the first enum literal of the real enum. Fixes mockito#2984
Mockito can't mock abstract enums in Java 15 or later because they are now marked as sealed. So Mockito reports that now with a better error message. Fixes #2984
The following code works:
The following code fails:
with the following error:
Mockito Version: 5.3.0
The text was updated successfully, but these errors were encountered: