/* * 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. * *

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

* * @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; 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 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. * *
  • 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() { 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()); } }