Skip to content
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 #3167

Merged
merged 1 commit into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -425,15 +425,15 @@ public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {

private <T> RuntimeException prettifyFailure(
MockCreationSettings<T> mockFeatures, Exception generationFailed) {
if (mockFeatures.getTypeToMock().isArray()) {
Class<T> typeToMock = mockFeatures.getTypeToMock();
if (typeToMock.isArray()) {
throw new MockitoException(
join("Arrays cannot be mocked: " + mockFeatures.getTypeToMock() + ".", ""),
generationFailed);
join("Arrays cannot be mocked: " + typeToMock + ".", ""), generationFailed);
}
if (Modifier.isFinal(mockFeatures.getTypeToMock().getModifiers())) {
if (Modifier.isFinal(typeToMock.getModifiers())) {
throw new MockitoException(
join(
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
"Mockito cannot mock this class: " + typeToMock + ".",
"Can not mock final classes with the following settings :",
" - explicit serialization (e.g. withSettings().serializable())",
" - extra interfaces (e.g. withSettings().extraInterfaces(...))",
Expand All @@ -444,10 +444,18 @@ private <T> RuntimeException prettifyFailure(
"Underlying exception : " + generationFailed),
generationFailed);
}
if (Modifier.isPrivate(mockFeatures.getTypeToMock().getModifiers())) {
if (TypeSupport.INSTANCE.isSealed(typeToMock) && typeToMock.isEnum()) {
throw new MockitoException(
join(
"Mockito cannot mock this class: " + typeToMock + ".",
"Sealed abstract enums can't be mocked. Since Java 15 abstract enums are declared sealed, which prevents mocking.",
"You can still return an existing enum literal from a stubbed method call."),
generationFailed);
}
if (Modifier.isPrivate(typeToMock.getModifiers())) {
throw new MockitoException(
join(
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
"Mockito cannot mock this class: " + typeToMock + ".",
"Most likely it is a private class that is not visible by Mockito",
"",
"You are seeing this disclaimer because Mockito is configured to create inlined mocks.",
Expand All @@ -457,7 +465,7 @@ private <T> RuntimeException prettifyFailure(
}
throw new MockitoException(
join(
"Mockito cannot mock this class: " + mockFeatures.getTypeToMock() + ".",
"Mockito cannot mock this class: " + typeToMock + ".",
"",
"If you're not sure why you're getting this error, please open an issue on GitHub.",
"",
Expand Down
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();
}
}
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.*;

import org.junit.Test;
import org.mockito.exceptions.base.MockitoException;

public class DeepStubReturnsEnumJava21Test {

@Test
public void cant_mock_enum_class_in_Java21_Issue_2984() {
assertThatThrownBy(
() -> {
mock(TestEnum.class);
})
.isInstanceOf(MockitoException.class)
.hasMessageContaining("Sealed abstract enums can't be mocked.")
.hasCauseInstanceOf(MockitoException.class);
}

@Test
public void cant_mock_enum_class_as_deep_stub_in_Java21_Issue_2984() {
assertThatThrownBy(
() -> {
mock(TestEnum.class, RETURNS_DEEP_STUBS);
})
.isInstanceOf(MockitoException.class)
.hasMessageContaining("Sealed abstract enums can't be mocked.")
.hasCauseInstanceOf(MockitoException.class);
}

@Test
public void deep_stub_cant_mock_enum_with_abstract_method_in_Java21_Issue_2984() {
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
assertThatThrownBy(
() -> {
mock.getTestEnum();
})
.isInstanceOf(MockitoException.class)
.hasMessageContaining("Sealed abstract enums can't be mocked.")
.hasCauseInstanceOf(MockitoException.class);
}

@Test
public void deep_stub_can_override_mock_enum_with_abstract_method_in_Java21_Issue_2984() {
final var mock = mock(TestClass.class, RETURNS_DEEP_STUBS);
// We need the doReturn() because when calling when(mock.getTestEnum()) it will already
// throw an exception.
doReturn(TestEnum.A).when(mock).getTestEnum();

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_Java21_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_Java21_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 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";
}
}
}