org.powermock.api.mockito.internal.invocationcontrol.MockitoMethodInvocationControl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powermock-api-mockito Show documentation
Show all versions of powermock-api-mockito Show documentation
PowerMock API for Mockito 1.+..
/*
* 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;
}
}