org.powermock.api.mockito.internal.invocation.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.invocation;
import org.mockito.Mockito;
import org.mockito.exceptions.base.MockitoAssertionError;
import org.mockito.exceptions.misusing.NotAMockException;
import org.mockito.internal.InternalMockHandler;
import org.mockito.internal.creation.DelegatingMethod;
import org.mockito.internal.debugging.Localized;
import org.mockito.internal.exceptions.stacktrace.StackTraceFilter;
import org.mockito.internal.invocation.InvocationImpl;
import org.mockito.internal.invocation.MatchersBinder;
import org.mockito.internal.invocation.realmethod.CleanTraceRealMethod;
import org.mockito.internal.invocation.realmethod.RealMethod;
import org.mockito.internal.progress.MockingProgress;
import org.mockito.internal.progress.SequenceNumber;
import org.mockito.internal.progress.ThreadSafeMockingProgress;
import org.mockito.internal.stubbing.InvocationContainer;
import org.mockito.internal.verification.VerificationDataImpl;
import org.mockito.internal.verification.VerificationModeFactory;
import org.mockito.invocation.Invocation;
import org.mockito.invocation.MockHandler;
import org.mockito.verification.VerificationMode;
import org.powermock.api.mockito.internal.verification.StaticMockAwareVerificationMode;
import org.powermock.api.mockito.repackaged.MethodInterceptorFilter;
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.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;
/*
* This field is required to fix the problem was that finalize methods could be called before an expected method
* because the GC kicked in too soon since now object held a reference to the mock.
* Even if it is not used in class we still need keep reference to mock object to prevent calling `finalize`
* method.
*/
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 {@code 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
* {@code methodsToMock} is null or empty, all methods for
* the {@code 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
* {@code 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 {@code 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
* {@code methodsToMock} is null or empty, all methods for
* the {@code invocationHandler} are considered to be
*/
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;
}
@Override
public boolean isMocked(Method method) {
return mockedMethods == null || (mockedMethods.contains(method));
}
private boolean isInVerificationMode() {
return getVerificationMode() != null;
}
private VerificationMode getVerificationMode() {
try {
MockingProgress progress = 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();
}
}
@Override
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 filter the stack-trace to check if "Mockito" exists as a stack trace element. (The filter method
* remove all Mocktio stack trace elements). If the filtered stack trace length is not equal to the original stack trace length
* this means that the call has been caught by Mockito.
*/
final StackTraceElement[] filteredStackTrace = filter.filter(stackTrace, true);
return filteredStackTrace.length != stackTrace.length;
}
private Object performIntercept(MethodInterceptorFilter invocationHandler, final Object interceptionObject,
final Method method, Object[] arguments) throws Throwable {
MockHandler mockHandler = invocationHandler.getHandler();
final CleanTraceRealMethod cglibProxyRealMethod = new CleanTraceRealMethod(new MockedRealMethod(method, interceptionObject));
Invocation invocation = new InvocationImpl(
interceptionObject,
new DelegatingMethod(method),
arguments,
SequenceNumber.next(),
cglibProxyRealMethod) {
private static final long serialVersionUID = -3679957412502758558L;
@Override
public String toString() {
return new ToStringGenerator().generate(getMock(), getMethod(), getArguments());
}
};
try {
return replaceMatchersBinderIfNeeded(mockHandler).handle(invocation);
} catch (NotAMockException e) {
if(invocation.getMock().getClass().getName().startsWith("java.") && MockRepository.getInstanceMethodInvocationControl(invocation.getMock()) != null) {
return invocation.callRealMethod();
} else {
throw e;
}
} catch (MockitoAssertionError e) {
InvocationControlAssertionError.updateErrorMessageForMethodInvocation(e);
throw e;
}
}
private MockHandler replaceMatchersBinderIfNeeded(MockHandler mockHandler) {
if(!Whitebox.getFieldsOfType(mockHandler, MatchersBinder.class).isEmpty()) {
Whitebox.setInternalState(mockHandler, new PowerMockMatchersBinder());
} else if(!Whitebox.getFieldsOfType(mockHandler, InternalMockHandler.class).isEmpty()) {
final MockHandler internalMockHandler = Whitebox.getInternalState(mockHandler, MockHandler.class);
return replaceMatchersBinderIfNeeded(internalMockHandler);
}
return mockHandler;
}
@Override
public Object replay(Object... mocks) {
throw new IllegalStateException("Internal error: No such thing as replay exists in Mockito.");
}
@Override
public Object reset(Object... mocks) {
throw new IllegalStateException("Internal error: No such thing as reset exists in Mockito.");
}
@Override
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 MockHandler mockHandler = methodInterceptorFilter.getHandler();
if (mockHandler instanceof MockHandler) {
InvocationContainer invocationContainer = Whitebox.invokeMethod(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;
} catch (Exception e) {
throw new RuntimeException("PowerMock internal error",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;
}
private static class MockedRealMethod implements RealMethod {
private final Method method;
private final Object interceptionObject;
public MockedRealMethod(Method method, Object interceptionObject) {
this.method = method;
this.interceptionObject = interceptionObject;
}
@Override
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.
*/
notMockNextCallIfRequired();
try {
return method.invoke(target, arguments);
} catch (InvocationTargetException e) {
SafeExceptionRethrower.safeRethrow(e.getCause());
}
return null;
}
private void notMockNextCallIfRequired() {
final Class> type = Whitebox.getType(interceptionObject);
if (isNextCallShouldNotBeMocked(type)) {
MockRepository.putAdditionalState(MockGateway.DONT_MOCK_NEXT_CALL, true);
}
}
private boolean isNextCallShouldNotBeMocked(Class> type) {
return !isFinalSystemJavaClass(type);
}
private boolean isFinalSystemJavaClass(Class> type) {
return type.getName().startsWith("java.") && Modifier.isFinal(type.getModifiers());
}
}
}