All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl Maven / Gradle / Ivy

There is a newer version: 1.7.4
Show newest version
/*
 * Copyright 2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.powermock.api.mockito.internal.invocationcontrol;

import org.hamcrest.Matcher;
import org.mockito.Mockito;
import org.mockito.exceptions.base.MockitoAssertionError;
import org.mockito.internal.MockHandler;
import org.mockito.internal.MockitoInvocationHandler;
import org.mockito.internal.creation.DelegatingMethod;
import org.mockito.internal.creation.MethodInterceptorFilter;
import org.mockito.internal.debugging.Localized;
import org.mockito.internal.exceptions.base.StackTraceFilter;
import org.mockito.internal.invocation.Invocation;
import org.mockito.internal.invocation.realmethod.FilteredCGLIBProxyRealMethod;
import org.mockito.internal.invocation.realmethod.RealMethod;
import org.mockito.internal.matchers.MatchersPrinter;
import org.mockito.internal.progress.MockingProgress;
import org.mockito.internal.progress.SequenceNumber;
import org.mockito.internal.progress.ThreadSafeMockingProgress;
import org.mockito.internal.reporting.PrintSettings;
import org.mockito.internal.stubbing.InvocationContainer;
import org.mockito.internal.verification.VerificationDataImpl;
import org.mockito.internal.verification.VerificationModeFactory;
import org.mockito.verification.VerificationMode;
import org.powermock.api.mockito.internal.verification.StaticMockAwareVerificationMode;
import org.powermock.api.support.SafeExceptionRethrower;
import org.powermock.core.MockGateway;
import org.powermock.core.MockRepository;
import org.powermock.core.spi.MethodInvocationControl;
import org.powermock.reflect.Whitebox;
import org.powermock.reflect.internal.WhiteboxImpl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A Mockito implementation of the {@link MethodInvocationControl} interface.
 */
public class MockitoMethodInvocationControl implements MethodInvocationControl {

    private final MethodInterceptorFilter methodInterceptorFilter;

    private final Set mockedMethods;
    private final Object delegator;
    private final Object mockInstance;

    /**
     * Creates a new instance.
     *
     * @param methodInterceptionFilter
     *            The methodInterceptionFilter to be associated with this
     *            instance.
     * @param mockInstance
     *          The actual mock instance. May be null. Even
     *            though the mock instance may not be used it's needed to keep a
     *            reference to this object otherwise it may be garbage collected
     *            in some situations. For example when mocking static methods we
     *            don't return the mock object and thus it will be garbage
     *            collected (and thus the finalize method will be invoked which
     *            will be caught by the proxy and the test will fail because we
     *            haven't setup expectations for this method) because then that
     *            object has no reference. In order to avoid this we keep a
     *            reference to this instance here.
     * @param methodsToMock
     *            The methods that are mocked for this instance. If
     *            methodsToMock is null or empty, all methods for
     *            the invocationHandler are considered to be
     *            mocked.
     */
    public MockitoMethodInvocationControl(MethodInterceptorFilter methodInterceptionFilter, Object mockInstance,  Method... methodsToMock) {
        this(methodInterceptionFilter,  null, mockInstance,  methodsToMock);
    }

    /**
     * Creates a new instance with a delegator. This delegator may be
     * null (if it is then no calls will be forwarded to this
     * instance). If a delegator exists (i.e. not null) all non-mocked calls
     * will be delegated to that instance.
     *
     * @param methodInterceptionFilter
     *            The methodInterceptionFilter to be associated with this
     *            instance.
     * @param delegator
     *            If the user spies on an instance the original instance must be
     *            injected here.
     * @param mockInstance
     *            The actual mock instance. May be null. Even
     *            though the mock instance may not be used it's needed to keep a
     *            reference to this object otherwise it may be garbage collected
     *            in some situations. For example when mocking static methods we
     *            don't return the mock object and thus it will be garbage
     *            collected (and thus the finalize method will be invoked which
     *            will be caught by the proxy and the test will fail because we
     *            haven't setup expectations for this method) because then that
     *            object has no reference. In order to avoid this we keep a
     *            reference to this instance here.
     * @param methodsToMock
     *            The methods that are mocked for this instance. If
     *            methodsToMock is null or empty, all methods for
     *            the invocationHandler are considered to be
     *            mocked.
     */
    public MockitoMethodInvocationControl(MethodInterceptorFilter methodInterceptionFilter, Object delegator,
                                          Object mockInstance, Method... methodsToMock) {
        if (methodInterceptionFilter == null) {
            throw new IllegalArgumentException("Invocation Handler cannot be null.");
        }

        this.mockedMethods = toSet(methodsToMock);
        this.mockInstance = mockInstance;
        this.delegator = delegator;
        this.methodInterceptorFilter = methodInterceptionFilter;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isMocked(Method method) {
        return mockedMethods == null || (mockedMethods != null && mockedMethods.contains(method));
    }

    private boolean isInVerificationMode() {
        return getVerificationMode() != null;
    }

    private VerificationMode getVerificationMode() {
        try {
            MockingProgress progress = (MockingProgress) Whitebox.invokeMethod(ThreadSafeMockingProgress.class,
                    "threadSafely");
            return getVerificationModeFromMockProgress(progress);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    private VerificationMode getVerificationModeFromMockProgress(MockingProgress mockingProgress) {
        if (mockingProgress == null) {
            return null;
        }
        if (mockingProgress instanceof ThreadSafeMockingProgress) {
            ThreadLocal threadLocal = Whitebox.getInternalState(mockingProgress, ThreadLocal.class);
            return getVerificationModeFromMockProgress(threadLocal.get());
        } else {
            Localized verificationMode = Whitebox.getInternalState(mockingProgress, Localized.class);
            return verificationMode == null ? null : verificationMode.getObject();
        }
    }

    public Object invoke(final Object obj, final Method method, final Object[] arguments) throws Throwable {
        /*
           * If we come here and it means that the class has been modified by
           * PowerMock. If this handler has a delegator (i.e. is in spy mode in
           * the current implementation) and it has been caught by the Mockito
           * proxy before our Mockgateway we need to know if the method is private
           * or not. Because if the previously described preconditions are met and
           * the method is not private it means that Mockito has already processed
           * the method invocation and we should NOT delegate the call to Mockito
           * again (thus we return proceed). If we would do that Mockito will
           * receive multiple method invocations to proxy for each method
           * invocation. For privately spied methods Mockito haven't received the
           * invocation and thus we should delegate the call to the Mockito proxy.
           */
        final Object returnValue;
        final int methodModifiers = method.getModifiers();
        if (hasDelegator() && !Modifier.isPrivate(methodModifiers) && !Modifier.isFinal(methodModifiers)
                && !Modifier.isStatic(methodModifiers) && hasBeenCaughtByMockitoProxy()) {
            returnValue = MockGateway.PROCEED;
        } else {
            boolean inVerificationMode = isInVerificationMode();
            if (WhiteboxImpl.isClass(obj) && inVerificationMode) {
                handleStaticVerification((Class) obj);
            }
            returnValue = performIntercept(methodInterceptorFilter, obj, method, arguments);
            if (returnValue == null) {
                return MockGateway.SUPPRESS;
            }
        }
        return returnValue;
    }

    private void handleStaticVerification(Class cls) {
        VerificationMode verificationMode = getVerificationMode();
        if (verificationMode instanceof StaticMockAwareVerificationMode) {
            ((StaticMockAwareVerificationMode) verificationMode).setClassMock(cls);
        }
    }

    private boolean hasBeenCaughtByMockitoProxy() {
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        StackTraceFilter filter = new StackTraceFilter();
        /*
           * We loop through all stack trace elements and see if it's "bad". Bad
           * means that the stack trance is cluttered with Mockito proxy
           * invocations which is why we know that the invocation has been caught
           * by the proxy if isBad returns true.
           */
        for (StackTraceElement stackTraceElement : stackTrace) {
            if (filter.isBad(stackTraceElement)) {
                return true;
            }
        }
        return false;
    }

    private Object performIntercept(MethodInterceptorFilter invocationHandler, final Object interceptionObject,
                                    final Method method, Object[] arguments) throws Throwable {
        MockitoInvocationHandler mockHandler = invocationHandler.getHandler();

        final FilteredCGLIBProxyRealMethod cglibProxyRealMethod = new FilteredCGLIBProxyRealMethod(new RealMethod() {
            private static final long serialVersionUID = 4564320968038564170L;

            public Object invoke(Object target, Object[] arguments) throws Throwable {
                /*
                     * Instruct the MockGateway to don't intercept the next call.
                     * The reason is that when Mockito is spying on objects it
                     * should call the "real method" (which is proxied by Mockito
                     * anyways) so that we don't end up in here one more time which
                     * causes infinite recursion. This should not be done if the
                     * interceptionObject is a final system class because these are
                     * never caught by the Mockito proxy.
                     */
                final Class type = Whitebox.getType(interceptionObject);
                final boolean isFinalSystemClass = type.getName().startsWith("java.")
                        && Modifier.isFinal(type.getModifiers());
                if (!isFinalSystemClass) {
                    MockRepository.putAdditionalState(MockGateway.DONT_MOCK_NEXT_CALL, true);
                }
                try {
                    return method.invoke(target, arguments);
                } catch (InvocationTargetException e) {
                    SafeExceptionRethrower.safeRethrow(e.getCause());
                }
                return null;
            }
        });
        // }
        Invocation invocation = new Invocation(interceptionObject, new DelegatingMethod(method), arguments,
                SequenceNumber.next(), cglibProxyRealMethod) {
            private static final long serialVersionUID = -3679957412502758558L;

            /**
             * We need to override this method because normally the String
             * "method" is assembled by calling the "qualifiedName" method but
             * this is not possible in our case. The reason is that the
             * qualifiedName method does
             *
             * 
             * new MockUtil().getMockName(mock)
             * 
* * which later will call the "isMockitoMock" method which will * return false and an exception will be thrown. The reason why * "isMockitoMock" returns false is that the mock is not created by * the Mockito CGLib Enhancer in case of static methods. */ @Override protected String toString(@SuppressWarnings("rawtypes") List matchers, PrintSettings printSettings) { MatchersPrinter matchersPrinter = new MatchersPrinter(); String method = Whitebox.getType(getMock()).getName() + "." + getMethodName(); String invocation = method + matchersPrinter.getArgumentsLine(matchers, printSettings); if (printSettings.isMultiline() || (!matchers.isEmpty() && invocation.length() > Whitebox. getInternalState( Invocation.class, "MAX_LINE_LENGTH"))) { return method + matchersPrinter.getArgumentsBlock(matchers, printSettings); } else { return invocation; } } }; try { return mockHandler.handle(invocation); } catch (MockitoAssertionError e) { InvocationControlAssertionError.updateErrorMessageForMethodInvocation(e); throw e; } } public Object replay(Object... mocks) { throw new IllegalStateException("Internal error: No such thing as replay exists in Mockito."); } public Object reset(Object... mocks) { throw new IllegalStateException("Internal error: No such thing as reset exists in Mockito."); } public Object verify(Object... mocks) { if (mocks == null || mocks.length != 1) { throw new IllegalArgumentException("Must supply one mock to the verify method."); } return Mockito.verify(mocks[0]); } public void verifyNoMoreInteractions() { try { final MockitoInvocationHandler mockHandler = methodInterceptorFilter.getHandler(); if (mockHandler instanceof MockHandler) { InvocationContainer invocationContainer = ((MockHandler) mockHandler).getInvocationContainer(); VerificationDataImpl data = new VerificationDataImpl(invocationContainer, null); VerificationModeFactory.noMoreInteractions().verify(data); } else { throw new RuntimeException( "Cannot perform verifyNoMoreInteractions because of unknown mockhandler type " + mockHandler.getClass()); } } catch (MockitoAssertionError e) { InvocationControlAssertionError.updateErrorMessageForVerifyNoMoreInteractions(e); throw e; } } private Set toSet(Method... methods) { return methods == null ? null : new HashSet(Arrays.asList(methods)); } private boolean hasDelegator() { return delegator != null; } public MethodInterceptorFilter getInvocationHandler() { return methodInterceptorFilter; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy