/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.stubbing.defaultanswers;
import static org.mockito.Mockito.withSettings;
import static org.mockito.internal.util.MockUtil.typeMockabilityOf;
import java.io.IOException;
import java.io.Serializable;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.internal.MockitoCore;
import org.mockito.internal.creation.settings.CreationSettings;
import org.mockito.internal.stubbing.InvocationContainerImpl;
import org.mockito.internal.stubbing.StubbedInvocationMatcher;
import org.mockito.internal.util.MockUtil;
import org.mockito.internal.util.reflection.GenericMetadataSupport;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.mock.MockCreationSettings;
import org.mockito.stubbing.Answer;
/**
* Returning deep stub implementation.
*
* Will return previously created mock if the invocation matches.
*
*
Supports nested generic information, with this answer you can write code like this :
*
*
* interface GenericsNest<K extends Comparable<K> & Cloneable> extends Map<K, Set<Number>> {}
*
* GenericsNest<?> mock = mock(GenericsNest.class, new ReturnsGenericDeepStubs());
* Number number = mock.entrySet().iterator().next().getValue().iterator().next();
*
*
*
* However this answer does not support generics information when the mock has been deserialized.
*
* @see org.mockito.Mockito#RETURNS_DEEP_STUBS
* @see org.mockito.Answers#RETURNS_DEEP_STUBS
*/
public class ReturnsDeepStubs implements Answer, Serializable {
private static final long serialVersionUID = -7105341425736035847L;
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
GenericMetadataSupport returnTypeGenericMetadata =
actualParameterizedType(invocation.getMock())
.resolveGenericReturnType(invocation.getMethod());
MockCreationSettings mockSettings = MockUtil.getMockSettings(invocation.getMock());
Class rawType = returnTypeGenericMetadata.rawType();
final var emptyValue = ReturnsEmptyValues.returnCommonEmptyValueFor(rawType);
if (emptyValue != null) {
return emptyValue;
}
if (!typeMockabilityOf(rawType, mockSettings.getMockMaker()).mockable()) {
if (invocation.getMethod().getReturnType().equals(rawType)) {
return delegate().answer(invocation);
} else {
return delegate().returnValueFor(rawType);
}
}
// When dealing with erased generics, we only receive the Object type as rawType. At this
// point, there is nothing to salvage for Mockito. Instead of trying to be smart and
// generate
// a mock that would potentially match the return signature, instead return `null`. This
// is valid per the CheckCast JVM instruction and is better than causing a
// ClassCastException
// on runtime.
if (rawType.equals(Object.class) && !returnTypeGenericMetadata.hasRawExtraInterfaces()) {
return null;
}
return deepStub(invocation, returnTypeGenericMetadata);
}
private Object deepStub(
InvocationOnMock invocation, GenericMetadataSupport returnTypeGenericMetadata)
throws Throwable {
InvocationContainerImpl container = MockUtil.getInvocationContainer(invocation.getMock());
Answer existingAnswer = container.findStubbedAnswer();
if (existingAnswer != null) {
return existingAnswer.answer(invocation);
}
// record deep stub answer
StubbedInvocationMatcher stubbing =
recordDeepStubAnswer(
newDeepStubMock(returnTypeGenericMetadata, invocation.getMock()),
container);
// deep stubbing creates a stubbing and immediately uses it
// so the stubbing is actually used by the same invocation
stubbing.markStubUsed(stubbing.getInvocation());
return stubbing.answer(invocation);
}
/**
* Creates a mock using the Generics Metadata.
*
* Finally as we want to mock the actual type, but we want to pass along the contextual generics meta-data
* that was resolved for the current return type, for this to happen we associate to the mock an new instance of
* {@link ReturnsDeepStubs} answer in which we will store the returned type generic metadata.
*
* @param returnTypeGenericMetadata The metadata to use to create the new mock.
* @param parentMock The parent of the current deep stub mock.
* @return The mock
*/
private Object newDeepStubMock(
GenericMetadataSupport returnTypeGenericMetadata, Object parentMock) {
MockCreationSettings parentMockSettings = MockUtil.getMockSettings(parentMock);
return mockitoCore()
.mock(
returnTypeGenericMetadata.rawType(),
withSettingsUsing(returnTypeGenericMetadata, parentMockSettings));
}
private MockSettings withSettingsUsing(
GenericMetadataSupport returnTypeGenericMetadata,
MockCreationSettings parentMockSettings) {
MockSettings mockSettings =
returnTypeGenericMetadata.hasRawExtraInterfaces()
? withSettings()
.extraInterfaces(returnTypeGenericMetadata.rawExtraInterfaces())
: withSettings();
return propagateSerializationSettings(mockSettings, parentMockSettings)
.defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata))
.mockMaker(parentMockSettings.getMockMaker());
}
private MockSettings propagateSerializationSettings(
MockSettings mockSettings, MockCreationSettings parentMockSettings) {
return mockSettings.serializable(parentMockSettings.getSerializableMode());
}
private ReturnsDeepStubs returnsDeepStubsAnswerUsing(
final GenericMetadataSupport returnTypeGenericMetadata) {
return new ReturnsDeepStubsSerializationFallback(returnTypeGenericMetadata);
}
private StubbedInvocationMatcher recordDeepStubAnswer(
final Object mock, InvocationContainerImpl container) {
DeeplyStubbedAnswer answer = new DeeplyStubbedAnswer(mock);
return container.addAnswer(answer, false, null);
}
protected GenericMetadataSupport actualParameterizedType(Object mock) {
CreationSettings mockSettings =
(CreationSettings) MockUtil.getMockHandler(mock).getMockSettings();
return GenericMetadataSupport.inferFrom(mockSettings.getTypeToMock());
}
private static class ReturnsDeepStubsSerializationFallback extends ReturnsDeepStubs
implements Serializable {
@SuppressWarnings("serial") // not gonna be serialized
private final GenericMetadataSupport returnTypeGenericMetadata;
public ReturnsDeepStubsSerializationFallback(
GenericMetadataSupport returnTypeGenericMetadata) {
this.returnTypeGenericMetadata = returnTypeGenericMetadata;
}
@Override
protected GenericMetadataSupport actualParameterizedType(Object mock) {
return returnTypeGenericMetadata;
}
/**
* Generics support and serialization with deep stubs don't work together.
*
* The issue is that GenericMetadataSupport is not serializable because
* the type elements inferred via reflection are not serializable. Supporting
* serialization would require to replace all types coming from the Java reflection
* with our own and still managing type equality with the JDK ones.
*/
private Object writeReplace() throws IOException {
return Mockito.RETURNS_DEEP_STUBS;
}
}
private static class DeeplyStubbedAnswer implements Answer, Serializable {
@SuppressWarnings(
"serial") // serialization will fail with a nice message if mock not serializable
private final Object mock;
DeeplyStubbedAnswer(Object mock) {
this.mock = mock;
}
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return mock;
}
}
private static MockitoCore mockitoCore() {
return LazyHolder.MOCKITO_CORE;
}
private static ReturnsEmptyValues delegate() {
return LazyHolder.DELEGATE;
}
private static class LazyHolder {
private static final MockitoCore MOCKITO_CORE = new MockitoCore();
private static final ReturnsEmptyValues DELEGATE = new ReturnsEmptyValues();
}
}