blob: b08af1b10200494601772bb7a1dfa040509260d5 [file] [log] [blame]
/*
* Copyright (c) 2007 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.stubbing.defaultanswers;
import org.mockito.MockSettings;
import org.mockito.internal.InternalMockHandler;
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.stubbing.Answer;
import java.io.Serializable;
import static org.mockito.Mockito.withSettings;
/**
* Returning deep stub implementation.
*
* Will return previously created mock if the invocation matches.
*
* <p>Supports nested generic information, with this answer you can write code like this :
*
* <pre class="code"><code class="java">
* interface GenericsNest&lt;K extends Comparable&lt;K&gt; & Cloneable&gt; extends Map&lt;K, Set&lt;Number&gt;&gt; {}
*
* GenericsNest&lt;?&gt; mock = mock(GenericsNest.class, new ReturnsGenericDeepStubs());
* Number number = mock.entrySet().iterator().next().getValue().iterator().next();
* </code></pre>
* </p>
*
* @see org.mockito.Mockito#RETURNS_DEEP_STUBS
* @see org.mockito.Answers#RETURNS_DEEP_STUBS
*/
public class ReturnsDeepStubs implements Answer<Object>, Serializable {
private static final long serialVersionUID = -7105341425736035847L;
private MockitoCore mockitoCore = new MockitoCore();
private ReturnsEmptyValues delegate = new ReturnsEmptyValues();
public Object answer(InvocationOnMock invocation) throws Throwable {
GenericMetadataSupport returnTypeGenericMetadata =
actualParameterizedType(invocation.getMock()).resolveGenericReturnType(invocation.getMethod());
Class<?> rawType = returnTypeGenericMetadata.rawType();
if (!mockitoCore.isTypeMockable(rawType)) {
return delegate.returnValueFor(rawType);
}
return getMock(invocation, returnTypeGenericMetadata);
}
private Object getMock(InvocationOnMock invocation, GenericMetadataSupport returnTypeGenericMetadata) throws Throwable {
InternalMockHandler<Object> handler = new MockUtil().getMockHandler(invocation.getMock());
InvocationContainerImpl container = (InvocationContainerImpl) handler.getInvocationContainer();
// matches invocation for verification
for (StubbedInvocationMatcher stubbedInvocationMatcher : container.getStubbedInvocations()) {
if(container.getInvocationForStubbing().matches(stubbedInvocationMatcher.getInvocation())) {
return stubbedInvocationMatcher.answer(invocation);
}
}
// deep stub
return recordDeepStubMock(createNewDeepStubMock(returnTypeGenericMetadata), container);
}
/**
* Creates a mock using the Generics Metadata.
*
* <li>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.
* @return The mock
*/
private Object createNewDeepStubMock(GenericMetadataSupport returnTypeGenericMetadata) {
return mockitoCore.mock(
returnTypeGenericMetadata.rawType(),
withSettingsUsing(returnTypeGenericMetadata)
);
}
private MockSettings withSettingsUsing(GenericMetadataSupport returnTypeGenericMetadata) {
MockSettings mockSettings =
returnTypeGenericMetadata.rawExtraInterfaces().length > 0 ?
withSettings().extraInterfaces(returnTypeGenericMetadata.rawExtraInterfaces())
: withSettings();
return mockSettings
.defaultAnswer(returnsDeepStubsAnswerUsing(returnTypeGenericMetadata));
}
private ReturnsDeepStubs returnsDeepStubsAnswerUsing(final GenericMetadataSupport returnTypeGenericMetadata) {
return new ReturnsDeepStubs() {
@Override
protected GenericMetadataSupport actualParameterizedType(Object mock) {
return returnTypeGenericMetadata;
}
};
}
private Object recordDeepStubMock(final Object mock, InvocationContainerImpl container) throws Throwable {
container.addAnswer(new Answer<Object>() {
public Object answer(InvocationOnMock invocation) throws Throwable {
return mock;
}
}, false);
return mock;
}
protected GenericMetadataSupport actualParameterizedType(Object mock) {
CreationSettings mockSettings = (CreationSettings) new MockUtil().getMockHandler(mock).getMockSettings();
return GenericMetadataSupport.inferFrom(mockSettings.getTypeToMock());
}
}