Skip to content

Commit 74633b8

Browse files
authored
Throw error when mocking sealed abstract enum (#3167)
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
1 parent 290a8e1 commit 74633b8

File tree

3 files changed

+228
-8
lines changed

3 files changed

+228
-8
lines changed

src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -425,15 +425,15 @@ public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
425425

426426
private <T> RuntimeException prettifyFailure(
427427
MockCreationSettings<T> mockFeatures, Exception generationFailed) {
428-
if (mockFeatures.getTypeToMock().isArray()) {
428+
Class<T> typeToMock = mockFeatures.getTypeToMock();
429+
if (typeToMock.isArray()) {
429430
throw new MockitoException(
430-
join("Arrays cannot be mocked: " + mockFeatures.getTypeToMock() + ".", ""),
431-
generationFailed);
431+
join("Arrays cannot be mocked: " + typeToMock + ".", ""), generationFailed);
432432
}
433-
if (Modifier.isFinal(mockFeatures.getTypeToMock().getModifiers())) {
433+
if (Modifier.isFinal(typeToMock.getModifiers())) {
434434
throw new MockitoException(
435435
join(
436-
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
436+
"Mockito cannot mock this class: " + typeToMock + ".",
437437
"Can not mock final classes with the following settings :",
438438
" - explicit serialization (e.g. withSettings().serializable())",
439439
" - extra interfaces (e.g. withSettings().extraInterfaces(...))",
@@ -444,10 +444,18 @@ private <T> RuntimeException prettifyFailure(
444444
"Underlying exception : " + generationFailed),
445445
generationFailed);
446446
}
447-
if (Modifier.isPrivate(mockFeatures.getTypeToMock().getModifiers())) {
447+
if (TypeSupport.INSTANCE.isSealed(typeToMock) && typeToMock.isEnum()) {
448+
throw new MockitoException(
449+
join(
450+
"Mockito cannot mock this class: " + typeToMock + ".",
451+
"Sealed abstract enums can't be mocked. Since Java 15 abstract enums are declared sealed, which prevents mocking.",
452+
"You can still return an existing enum literal from a stubbed method call."),
453+
generationFailed);
454+
}
455+
if (Modifier.isPrivate(typeToMock.getModifiers())) {
448456
throw new MockitoException(
449457
join(
450-
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
458+
"Mockito cannot mock this class: " + typeToMock + ".",
451459
"Most likely it is a private class that is not visible by Mockito",
452460
"",
453461
"You are seeing this disclaimer because Mockito is configured to create inlined mocks.",
@@ -457,7 +465,7 @@ private <T> RuntimeException prettifyFailure(
457465
}
458466
throw new MockitoException(
459467
join(
460-
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
468+
"Mockito cannot mock this class: " + typeToMock + ".",
461469
"",
462470
"If you're not sure why you're getting this error, please open an issue on GitHub.",
463471
"",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2023 Mockito contributors
3+
* This program is made available under the terms of the MIT License.
4+
*/
5+
package org.mockito.internal.stubbing.answers;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.when;
11+
12+
import org.junit.Test;
13+
14+
public class DeepStubReturnsEnumJava11Test {
15+
private static final String MOCK_VALUE = "Mock";
16+
17+
@Test
18+
public void deep_stub_can_mock_enum_getter_Issue_2984() {
19+
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
20+
when(mock.getTestEnum()).thenReturn(TestEnum.B);
21+
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B);
22+
}
23+
24+
@Test
25+
public void deep_stub_can_mock_enum_class_Issue_2984() {
26+
final var mock = mock(TestEnum.class, RETURNS_DEEP_STUBS);
27+
when(mock.getValue()).thenReturn(MOCK_VALUE);
28+
assertThat(mock.getValue()).isEqualTo(MOCK_VALUE);
29+
}
30+
31+
@Test
32+
public void deep_stub_can_mock_enum_method_Issue_2984() {
33+
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
34+
assertThat(mock.getTestEnum().getValue()).isEqualTo(null);
35+
36+
when(mock.getTestEnum().getValue()).thenReturn(MOCK_VALUE);
37+
assertThat(mock.getTestEnum().getValue()).isEqualTo(MOCK_VALUE);
38+
}
39+
40+
@Test
41+
public void mock_mocking_enum_getter_Issue_2984() {
42+
final var mock = mock(TestClass.class);
43+
when(mock.getTestEnum()).thenReturn(TestEnum.B);
44+
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B);
45+
assertThat(mock.getTestEnum().getValue()).isEqualTo("B");
46+
}
47+
48+
static class TestClass {
49+
TestEnum getTestEnum() {
50+
return TestEnum.A;
51+
}
52+
}
53+
54+
enum TestEnum {
55+
A {
56+
@Override
57+
String getValue() {
58+
return this.name();
59+
}
60+
},
61+
B {
62+
@Override
63+
String getValue() {
64+
return this.name();
65+
}
66+
},
67+
;
68+
69+
abstract String getValue();
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright (c) 2023 Mockito contributors
3+
* This program is made available under the terms of the MIT License.
4+
*/
5+
package org.mockito.internal.stubbing.answers;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
9+
import static org.mockito.Mockito.*;
10+
11+
import org.junit.Test;
12+
import org.mockito.exceptions.base.MockitoException;
13+
14+
public class DeepStubReturnsEnumJava21Test {
15+
16+
@Test
17+
public void cant_mock_enum_class_in_Java21_Issue_2984() {
18+
assertThatThrownBy(
19+
() -> {
20+
mock(TestEnum.class);
21+
})
22+
.isInstanceOf(MockitoException.class)
23+
.hasMessageContaining("Sealed abstract enums can't be mocked.")
24+
.hasCauseInstanceOf(MockitoException.class);
25+
}
26+
27+
@Test
28+
public void cant_mock_enum_class_as_deep_stub_in_Java21_Issue_2984() {
29+
assertThatThrownBy(
30+
() -> {
31+
mock(TestEnum.class, RETURNS_DEEP_STUBS);
32+
})
33+
.isInstanceOf(MockitoException.class)
34+
.hasMessageContaining("Sealed abstract enums can't be mocked.")
35+
.hasCauseInstanceOf(MockitoException.class);
36+
}
37+
38+
@Test
39+
public void deep_stub_cant_mock_enum_with_abstract_method_in_Java21_Issue_2984() {
40+
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
41+
assertThatThrownBy(
42+
() -> {
43+
mock.getTestEnum();
44+
})
45+
.isInstanceOf(MockitoException.class)
46+
.hasMessageContaining("Sealed abstract enums can't be mocked.")
47+
.hasCauseInstanceOf(MockitoException.class);
48+
}
49+
50+
@Test
51+
public void deep_stub_can_override_mock_enum_with_abstract_method_in_Java21_Issue_2984() {
52+
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
53+
// We need the doReturn() because when calling when(mock.getTestEnum()) it will already
54+
// throw an exception.
55+
doReturn(TestEnum.A).when(mock).getTestEnum();
56+
57+
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.A);
58+
assertThat(mock.getTestEnum().getValue()).isEqualTo("A");
59+
60+
assertThat(mockingDetails(mock.getTestEnum()).isMock()).isFalse();
61+
}
62+
63+
@Test
64+
public void deep_stub_can_mock_enum_without_method_in_Java21_Issue_2984() {
65+
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
66+
assertThat(mock.getTestNonAbstractEnum()).isNotNull();
67+
68+
assertThat(mockingDetails(mock.getTestNonAbstractEnum()).isMock()).isTrue();
69+
when(mock.getTestNonAbstractEnum()).thenReturn(TestNonAbstractEnum.B);
70+
assertThat(mock.getTestNonAbstractEnum()).isEqualTo(TestNonAbstractEnum.B);
71+
}
72+
73+
@Test
74+
public void deep_stub_can_mock_enum_without_abstract_method_in_Java21_Issue_2984() {
75+
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
76+
assertThat(mock.getTestNonAbstractEnumWithMethod()).isNotNull();
77+
assertThat(mock.getTestNonAbstractEnumWithMethod().getValue()).isNull();
78+
assertThat(mockingDetails(mock.getTestNonAbstractEnumWithMethod()).isMock()).isTrue();
79+
80+
when(mock.getTestNonAbstractEnumWithMethod().getValue()).thenReturn("Mock");
81+
assertThat(mock.getTestNonAbstractEnumWithMethod().getValue()).isEqualTo("Mock");
82+
83+
when(mock.getTestNonAbstractEnumWithMethod()).thenReturn(TestNonAbstractEnumWithMethod.B);
84+
assertThat(mock.getTestNonAbstractEnumWithMethod())
85+
.isEqualTo(TestNonAbstractEnumWithMethod.B);
86+
}
87+
88+
@Test
89+
public void mock_mocking_enum_getter_Issue_2984() {
90+
final var mock = mock(TestClass.class);
91+
when(mock.getTestEnum()).thenReturn(TestEnum.B);
92+
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B);
93+
assertThat(mock.getTestEnum().getValue()).isEqualTo("B");
94+
}
95+
96+
static class TestClass {
97+
TestEnum getTestEnum() {
98+
return TestEnum.A;
99+
}
100+
101+
TestNonAbstractEnumWithMethod getTestNonAbstractEnumWithMethod() {
102+
return TestNonAbstractEnumWithMethod.A;
103+
}
104+
105+
TestNonAbstractEnum getTestNonAbstractEnum() {
106+
return TestNonAbstractEnum.A;
107+
}
108+
}
109+
110+
enum TestEnum {
111+
A {
112+
@Override
113+
String getValue() {
114+
return this.name();
115+
}
116+
},
117+
B {
118+
@Override
119+
String getValue() {
120+
return this.name();
121+
}
122+
},
123+
;
124+
125+
abstract String getValue();
126+
}
127+
128+
enum TestNonAbstractEnum {
129+
A,
130+
B
131+
}
132+
133+
enum TestNonAbstractEnumWithMethod {
134+
A,
135+
B;
136+
137+
String getValue() {
138+
return "RealValue";
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)