forked from mockito/mockito
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Deep Stubs Incompatible With Mocking Enum
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
- Loading branch information
Showing
6 changed files
with
303 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/main/java/org/mockito/exceptions/base/MockitoCantMockEnumException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright (c) 2023 Mockito contributors | ||
* This program is made available under the terms of the MIT License. | ||
*/ | ||
package org.mockito.exceptions.base; | ||
|
||
/** | ||
* Raised by mockito to emit an error when an {@link Enum} shall be mocked, but the Java Runtime does not permit it. | ||
*/ | ||
public class MockitoCantMockEnumException extends MockitoException { | ||
|
||
private static final long serialVersionUID = 1L; | ||
|
||
public MockitoCantMockEnumException(String message, Throwable t) { | ||
super(message, t); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
src/test/java/org/mockito/internal/stubbing/answers/DeepStubReturnsEnumJava11Test.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright (c) 2023 Mockito contributors | ||
* This program is made available under the terms of the MIT License. | ||
*/ | ||
package org.mockito.internal.stubbing.answers; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
import org.junit.Test; | ||
|
||
public class DeepStubReturnsEnumJava11Test { | ||
private static final String MOCK_VALUE = "Mock"; | ||
|
||
@Test | ||
public void deep_stub_can_mock_enum_getter_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_class_Issue_2984() { | ||
final var mock = mock(TestEnum.class, RETURNS_DEEP_STUBS); | ||
when(mock.getValue()).thenReturn(MOCK_VALUE); | ||
assertThat(mock.getValue()).isEqualTo(MOCK_VALUE); | ||
} | ||
|
||
@Test | ||
public void deep_stub_can_mock_enum_method_Issue_2984() { | ||
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS); | ||
assertThat(mock.getTestEnum().getValue()).isEqualTo(null); | ||
|
||
when(mock.getTestEnum().getValue()).thenReturn(MOCK_VALUE); | ||
assertThat(mock.getTestEnum().getValue()).isEqualTo(MOCK_VALUE); | ||
} | ||
|
||
@Test | ||
public void mock_mocking_enum_getter_Issue_2984() { | ||
final var mock = mock(TestClass.class); | ||
when(mock.getTestEnum()).thenReturn(TestEnum.B); | ||
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B); | ||
assertThat(mock.getTestEnum().getValue()).isEqualTo("B"); | ||
} | ||
|
||
static class TestClass { | ||
TestEnum getTestEnum() { | ||
return TestEnum.A; | ||
} | ||
} | ||
|
||
enum TestEnum { | ||
A { | ||
@Override | ||
String getValue() { | ||
return this.name(); | ||
} | ||
}, | ||
B { | ||
@Override | ||
String getValue() { | ||
return this.name(); | ||
} | ||
}, | ||
; | ||
|
||
abstract String getValue(); | ||
} | ||
} |
141 changes: 141 additions & 0 deletions
141
src/test17/java/org/mockito/internal/stubbing/answers/DeepStubReturnsEnumJava17Test.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* | ||
* Copyright (c) 2023 Mockito contributors | ||
* This program is made available under the terms of the MIT License. | ||
*/ | ||
package org.mockito.internal.stubbing.answers; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.mockingDetails; | ||
import static org.mockito.Mockito.when; | ||
|
||
import org.junit.Test; | ||
import org.mockito.exceptions.base.MockitoCantMockEnumException; | ||
import org.mockito.exceptions.base.MockitoException; | ||
|
||
public class DeepStubReturnsEnumJava17Test { | ||
|
||
@Test | ||
public void cant_mock_enum_class_in_Java17_Issue_2984() { | ||
assertThatThrownBy( | ||
() -> { | ||
mock(TestEnum.class); | ||
}) | ||
.isInstanceOf(MockitoCantMockEnumException.class) | ||
.hasMessage( | ||
"\nMockito cannot mock this class: class org.mockito.internal.stubbing.answers.DeepStubReturnsEnumJava17Test$TestEnum.\n" | ||
+ "Sealed abstract enums can't be mocked. Since Java 15 abstract enums are declared sealed, which prevents mocking.") | ||
.hasCauseInstanceOf(MockitoException.class); | ||
} | ||
|
||
@Test | ||
public void cant_mock_enum_class_as_deep_stub_in_Java17_Issue_2984() { | ||
assertThatThrownBy( | ||
() -> { | ||
mock(TestEnum.class, RETURNS_DEEP_STUBS); | ||
}) | ||
.isInstanceOf(MockitoCantMockEnumException.class) | ||
.hasMessage( | ||
"\nMockito cannot mock this class: class org.mockito.internal.stubbing.answers.DeepStubReturnsEnumJava17Test$TestEnum.\n" | ||
+ "Sealed abstract enums can't be mocked. Since Java 15 abstract enums are declared sealed, which prevents mocking.") | ||
.hasCauseInstanceOf(MockitoException.class); | ||
} | ||
|
||
@Test | ||
public void deep_stub_cant_mock_enum_with_abstract_method_in_Java17_Issue_2984() { | ||
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS); | ||
// It returns the value of the first enum literal, because it is not mockable | ||
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.A); | ||
assertThat(mock.getTestEnum().getValue()).isEqualTo("A"); | ||
|
||
assertThat(mockingDetails(mock.getTestEnum()).isMock()).isFalse(); | ||
} | ||
|
||
@Test | ||
public void deep_stub_can_mock_enum_without_method_in_Java17_Issue_2984() { | ||
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS); | ||
assertThat(mock.getTestNonAbstractEnum()).isNotNull(); | ||
|
||
assertThat(mockingDetails(mock.getTestNonAbstractEnum()).isMock()).isTrue(); | ||
when(mock.getTestNonAbstractEnum()).thenReturn(TestNonAbstractEnum.B); | ||
assertThat(mock.getTestNonAbstractEnum()).isEqualTo(TestNonAbstractEnum.B); | ||
} | ||
|
||
@Test | ||
public void deep_stub_can_mock_enum_without_abstract_method_in_Java17_Issue_2984() { | ||
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS); | ||
assertThat(mock.getTestNonAbstractEnumWithMethod()).isNotNull(); | ||
assertThat(mock.getTestNonAbstractEnumWithMethod().getValue()).isNull(); | ||
assertThat(mockingDetails(mock.getTestNonAbstractEnumWithMethod()).isMock()).isTrue(); | ||
|
||
when(mock.getTestNonAbstractEnumWithMethod().getValue()).thenReturn("Mock"); | ||
assertThat(mock.getTestNonAbstractEnumWithMethod().getValue()).isEqualTo("Mock"); | ||
|
||
when(mock.getTestNonAbstractEnumWithMethod()).thenReturn(TestNonAbstractEnumWithMethod.B); | ||
assertThat(mock.getTestNonAbstractEnumWithMethod()) | ||
.isEqualTo(TestNonAbstractEnumWithMethod.B); | ||
} | ||
|
||
@Test | ||
public void deep_stub_can_mock_enum_getter_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 mock_mocking_enum_getter_Issue_2984() { | ||
final var mock = mock(TestClass.class); | ||
when(mock.getTestEnum()).thenReturn(TestEnum.B); | ||
assertThat(mock.getTestEnum()).isEqualTo(TestEnum.B); | ||
assertThat(mock.getTestEnum().getValue()).isEqualTo("B"); | ||
} | ||
|
||
static class TestClass { | ||
TestEnum getTestEnum() { | ||
return TestEnum.A; | ||
} | ||
|
||
TestNonAbstractEnumWithMethod getTestNonAbstractEnumWithMethod() { | ||
return TestNonAbstractEnumWithMethod.A; | ||
} | ||
|
||
TestNonAbstractEnum getTestNonAbstractEnum() { | ||
return TestNonAbstractEnum.A; | ||
} | ||
} | ||
|
||
enum TestEnum { | ||
A { | ||
@Override | ||
String getValue() { | ||
return this.name(); | ||
} | ||
}, | ||
B { | ||
@Override | ||
String getValue() { | ||
return this.name(); | ||
} | ||
}, | ||
; | ||
|
||
abstract String getValue(); | ||
} | ||
|
||
enum TestNonAbstractEnum { | ||
A, | ||
B | ||
} | ||
|
||
enum TestNonAbstractEnumWithMethod { | ||
A, | ||
B; | ||
|
||
String getValue() { | ||
return "RealValue"; | ||
} | ||
} | ||
} |