/* * Copyright (c) 2007 Mockito contributors * This program is made available under the terms of the MIT License. */ package org.mockito.internal.handler; import java.util.List; import org.mockito.internal.InternalMockHandler; import org.mockito.internal.creation.settings.CreationSettings; import org.mockito.internal.invocation.InvocationMatcher; import org.mockito.internal.invocation.MatchersBinder; import org.mockito.internal.listeners.StubbingLookupListener; import org.mockito.internal.stubbing.InvocationContainer; import org.mockito.internal.stubbing.InvocationContainerImpl; import org.mockito.internal.stubbing.OngoingStubbingImpl; import org.mockito.internal.stubbing.StubbedInvocationMatcher; import org.mockito.internal.stubbing.answers.DefaultAnswerValidator; import org.mockito.internal.verification.MockAwareVerificationMode; import org.mockito.internal.verification.VerificationDataImpl; import org.mockito.invocation.Invocation; import org.mockito.mock.MockCreationSettings; import org.mockito.stubbing.Answer; import org.mockito.verification.VerificationMode; import static org.mockito.internal.exceptions.Reporter.stubPassedToVerify; import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress; /** * Invocation handler set on mock objects. * * @param <T> type of mock object to handle */ public class MockHandlerImpl<T> implements InternalMockHandler<T> { private static final long serialVersionUID = -2917871070982574165L; InvocationContainerImpl invocationContainerImpl; MatchersBinder matchersBinder = new MatchersBinder(); private final MockCreationSettings<T> mockSettings; public MockHandlerImpl(MockCreationSettings<T> mockSettings) { this.mockSettings = mockSettings; this.matchersBinder = new MatchersBinder(); this.invocationContainerImpl = new InvocationContainerImpl( mockSettings); } public Object handle(Invocation invocation) throws Throwable { if (invocationContainerImpl.hasAnswersForStubbing()) { // stubbing voids with doThrow() or doAnswer() style InvocationMatcher invocationMatcher = matchersBinder.bindMatchers( mockingProgress().getArgumentMatcherStorage(), invocation ); invocationContainerImpl.setMethodForStubbing(invocationMatcher); return null; } VerificationMode verificationMode = mockingProgress().pullVerificationMode(); InvocationMatcher invocationMatcher = matchersBinder.bindMatchers( mockingProgress().getArgumentMatcherStorage(), invocation ); mockingProgress().validateState(); // if verificationMode is not null then someone is doing verify() if (verificationMode != null) { // We need to check if verification was started on the correct mock // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138) if (((MockAwareVerificationMode) verificationMode).getMock() == invocation.getMock()) { VerificationDataImpl data = createVerificationData(invocationContainerImpl, invocationMatcher); verificationMode.verify(data); return null; } else { // this means there is an invocation on a different mock. Re-adding verification mode // - see VerifyingWithAnExtraCallToADifferentMockTest (bug 138) mockingProgress().verificationStarted(verificationMode); } } // prepare invocation for stubbing invocationContainerImpl.setInvocationForPotentialStubbing(invocationMatcher); OngoingStubbingImpl<T> ongoingStubbing = new OngoingStubbingImpl<T>(invocationContainerImpl); mockingProgress().reportOngoingStubbing(ongoingStubbing); // look for existing answer for this invocation StubbedInvocationMatcher stubbedInvocation = invocationContainerImpl.findAnswerFor(invocation); notifyStubbedAnswerLookup(invocation, stubbedInvocation); if (stubbedInvocation != null) { stubbedInvocation.captureArgumentsFrom(invocation); return stubbedInvocation.answer(invocation); } else { Object ret = mockSettings.getDefaultAnswer().answer(invocation); DefaultAnswerValidator.validateReturnValueFor(invocation, ret); // redo setting invocation for potential stubbing in case of partial // mocks / spies. // Without it, the real method inside 'when' might have delegated // to other self method and overwrite the intended stubbed method // with a different one. The reset is required to avoid runtime exception that validates return type with stubbed method signature. invocationContainerImpl.resetInvocationForPotentialStubbing(invocationMatcher); return ret; } } public MockCreationSettings<T> getMockSettings() { return mockSettings; } public void setAnswersForStubbing(List<Answer<?>> answers) { invocationContainerImpl.setAnswersForStubbing(answers); } public InvocationContainer getInvocationContainer() { return invocationContainerImpl; } private VerificationDataImpl createVerificationData(InvocationContainerImpl invocationContainerImpl, InvocationMatcher invocationMatcher) { if (mockSettings.isStubOnly()) { throw stubPassedToVerify(); // this throws an exception } return new VerificationDataImpl(invocationContainerImpl, invocationMatcher); } private void notifyStubbedAnswerLookup(Invocation invocation, StubbedInvocationMatcher exception) { //TODO #793 - when completed, we should be able to get rid of the casting below List<StubbingLookupListener> listeners = ((CreationSettings) mockSettings).getStubbingLookupListeners(); for (StubbingLookupListener listener : listeners) { listener.onStubbingLookup(invocation, exception); } } }