Skip to content

Commit

Permalink
Default mock of Optional.isEmpty() returns true when using RETURN_DEE…
Browse files Browse the repository at this point in the history
…P_STUBS

ReturnsDeepStubs now answers with true on Optional.isEmpty()
when using RETURN_DEEP_STUBS.
But the User can still mock the methods of Optional.
In the normal mock case, for Optionals it returns as real Optional.

Fixes mockito#2865
  • Loading branch information
AndreasTu committed Aug 17, 2023
1 parent cb75cec commit 3118cc4
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 0 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +53,10 @@ public class ReturnsDeepStubs implements Answer<Object>, 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());
Expand Down Expand Up @@ -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:
* <ul>
* <li>{@link Optional}</li>
* <li>{@link OptionalLong}</li>
* <li>{@link OptionalDouble}</li>
* <li>{@link OptionalInt}</li>
* </ul>
*
* <p>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 {
Expand Down
@@ -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<String> 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<String> 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<String> 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<String> 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();
}
}

0 comments on commit 3118cc4

Please sign in to comment.