diff --git a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java index 8356c1b79b..e9acdf1543 100644 --- a/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java +++ b/src/main/java/org/mockito/internal/stubbing/defaultanswers/ReturnsDeepStubs.java @@ -9,6 +9,11 @@ import java.io.IOException; import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import org.mockito.MockSettings; import org.mockito.Mockito; @@ -48,6 +53,10 @@ public class ReturnsDeepStubs implements Answer, Serializable { @Override public Object answer(InvocationOnMock invocation) throws Throwable { + Object answer = handleDefaultAnswersForWellKnownTypes(invocation); + if (answer != null) { + return answer; + } GenericMetadataSupport returnTypeGenericMetadata = actualParameterizedType(invocation.getMock()) .resolveGenericReturnType(invocation.getMethod()); @@ -76,6 +85,34 @@ public Object answer(InvocationOnMock invocation) throws Throwable { return deepStub(invocation, returnTypeGenericMetadata); } + /** + * This returns default answers for well known methods of the following special types: + * + * + *

Issue #2865: Default mock of Optional is not empty when using RETURN_DEEP_STUBS + * + * @param invocation the invocation + * @return the answer or {@code null}, if no default answer was found. + */ + private Object handleDefaultAnswersForWellKnownTypes(InvocationOnMock invocation) { + Method method = invocation.getMethod(); + Class declaringClass = method.getDeclaringClass(); + if (declaringClass == Optional.class + || declaringClass == OptionalLong.class + || declaringClass == OptionalDouble.class + || declaringClass == OptionalInt.class) { + if (method.getName().equals("isEmpty") && method.getParameterCount() == 0) { + return true; + } + } + return null; + } + private Object deepStub( InvocationOnMock invocation, GenericMetadataSupport returnTypeGenericMetadata) throws Throwable { diff --git a/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsOptionalMocksTest.java b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsOptionalMocksTest.java new file mode 100644 index 0000000000..00bb7ca4c8 --- /dev/null +++ b/src/test/java/org/mockito/internal/stubbing/defaultanswers/ReturnsOptionalMocksTest.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2023 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.stubbing.defaultanswers; + +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.mockingDetails; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.junit.Test; + +public class ReturnsOptionalMocksTest { + private interface Type { + Optional getOptString(); + + OptionalLong getOptLong(); + + OptionalDouble getOptDouble(); + + OptionalInt getOptInt(); + } + + @Test + public void deepStubs_Optional_should_return_mocked_optional_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + assertThat(type.getOptString()).isNotEqualTo(Optional.empty()); + assertIsMock(type.getOptString()); + } + + @Test + public void deepStubs_OptionalLong_should_return_mocked_optional_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + assertThat(type.getOptLong()).isNotEqualTo(OptionalLong.empty()); + assertIsMock(type.getOptLong()); + } + + @Test + public void deepStubs_OptionalDouble_should_return_mocked_optional_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + assertThat(type.getOptDouble()).isNotEqualTo(OptionalDouble.empty()); + assertIsMock(type.getOptDouble()); + } + + @Test + public void deepStubs_OptionalInt_should_return_mocked_optional() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + assertThat(type.getOptInt()).isNotEqualTo(OptionalInt.empty()); + assertIsMock(type.getOptInt()); + } + + @Test + public void normal_mock_Optional_should_return_normal_optional_empty() { + final Type type = mock(Type.class); + assertThat(type.getOptString()).isEqualTo(Optional.empty()); + assertIsNoMock(type.getOptString()); + } + + @Test + public void normal_mock_OptionalLong_should_return_normal_optional_empty() { + final Type type = mock(Type.class); + assertThat(type.getOptLong()).isEqualTo(OptionalLong.empty()); + } + + @Test + public void normal_mock_OptionalDouble_should_return_normal_optional_empty() { + final Type type = mock(Type.class); + assertThat(type.getOptDouble()).isEqualTo(OptionalDouble.empty()); + } + + @Test + public void normal_mock_OptionalInt_should_return_normal_optional_empty() { + final Type type = mock(Type.class); + assertThat(type.getOptInt()).isEqualTo(OptionalInt.empty()); + } + + @Test + public void deepStubs_Optional_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + Optional opt = type.getOptString(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void deepStubs_Optional_isEmpty_is_mockable() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + Optional opt = type.getOptString(); + assertThat(opt.isEmpty()).isEqualTo(true); + when(opt.isEmpty()).thenReturn(false); + assertThat(opt.isEmpty()).isEqualTo(false); + verify(opt, times(2)).isEmpty(); + } + + @Test + public void deepStubs_OptionalLong_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + OptionalLong opt = type.getOptLong(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void deepStubs_OptionalDouble_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + OptionalDouble opt = type.getOptDouble(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void deepStubs_OptionalInt_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class, RETURNS_DEEP_STUBS); + OptionalInt opt = type.getOptInt(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void normal_mock_Optional_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class); + + Optional opt = type.getOptString(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void normal_mock_OptionalLong_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class); + + OptionalLong opt = type.getOptLong(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void normal_mock_OptionalDouble_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class); + + OptionalDouble opt = type.getOptDouble(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + @Test + public void normal_mock_OptionalInt_isPresent_isEmpty_Issue2865() { + final Type type = mock(Type.class); + + OptionalInt opt = type.getOptInt(); + assertThat(opt.isPresent()).isEqualTo(false); + assertThat(opt.isEmpty()).isEqualTo(true); + } + + private void assertIsMock(Object mock) { + assertThat(mockingDetails(mock).isMock()).isTrue(); + } + + private void assertIsNoMock(Object mock) { + assertThat(mockingDetails(mock).isMock()).isFalse(); + } +}