/* | |
* 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<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(); | |
* </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()); | |
} | |
} |