org.unitils.easymock.util.LenientMocksControl Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2008, Unitils.org
* 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.unitils.easymock.util;
import java.util.List;
import org.easymock.IAnswer;
import org.easymock.IArgumentMatcher;
import org.easymock.internal.IMocksControlState;
import org.easymock.internal.Invocation;
import org.easymock.internal.LastControl;
import org.easymock.internal.MocksControl;
import org.easymock.internal.Range;
import org.easymock.internal.RecordState;
import org.unitils.reflectionassert.ReflectionComparatorMode;
/**
* An EasyMock mock control that uses the reflection argument matcher for all arguments of a method invocation.
*
* No explicit argument matcher setting is needed (or allowed). This control will automatically report
* lenient reflection argument matchers. These matchers can apply some leniency when comparing expected and actual
* argument values.
*
* Setting the {@link ReflectionComparatorMode#IGNORE_DEFAULTS} mode will for example ignore all fields that
* have default values as expected values. E.g. if a null value is recorded as argument it will not be checked when
* the actual invocation occurs. The same applies for inner-fields of object arguments that contain default java values.
*
* Setting the {@link ReflectionComparatorMode#LENIENT_DATES} mode will ignore the actual date values of arguments and
* inner fields of arguments. It will only check whether both dates are null or both dates are not null. The actual
* date and hour do not matter.
*
* Setting the {@link ReflectionComparatorMode#LENIENT_ORDER} mode will ignore the actual order of collections and
* arrays arguments and inner fields of arguments. It will only check whether they both contain the same elements.
*
* @author Tim Ducheyne
* @author Filip Neven
* @see ReflectionComparatorMode
* @see org.unitils.reflectionassert.ReflectionComparator
*/
public class LenientMocksControl
extends MocksControl {
/***/
private static final long serialVersionUID = -4612378998272988410L;
/* The interceptor that wraps the record state */
private InvocationInterceptor invocationInterceptor;
/**
* Creates a default (no default returns and no order checking) mock control.
*
* @param modes
* the modes for the reflection argument matcher
*/
public LenientMocksControl(ReflectionComparatorMode... modes) {
this(org.easymock.MockType.DEFAULT, modes);
}
/**
* Creates a mock control.
*
* - Default mock type: no default return values and no order checking
* - Nice mock type: returns default values if no return value set, no order checking
* - Strict mock type: no default return values and strict order checking
*
*
* @param type
* the EasyMock mock type
* @param modes
* the modes for the reflection argument matcher
*/
public LenientMocksControl(org.easymock.MockType type, ReflectionComparatorMode... modes) {
super(type);
this.invocationInterceptor = new InvocationInterceptor(modes);
}
/**
* Overriden to be able to replace the record behavior that going to record all method invocations.
* The interceptor will make sure that reflection argument matchers will be reported for the
* arguments of all recorded method invocations.
*
* @return the state, wrapped in case of a RecordState
*/
@Override
public IMocksControlState getState() {
IMocksControlState mocksControlState = super.getState();
if (mocksControlState instanceof RecordState) {
invocationInterceptor.setRecordState((RecordState) mocksControlState);
return invocationInterceptor;
}
return mocksControlState;
}
/**
* A wrapper for the record state in easy mock that will intercept the invoke method
* so that it can install reflection argument matchers for all arguments of the recorded method invocation.
*
* The old easy mock way of having a single argument matcher for all arguments has been deprecated. Since
* EasyMock 2 each argument should have its own matcher. We however want to avoid having to set all
* matchers to the reflection argument matcher explicitly.
* Because some of the methods are declared final and some classes explicitly cast to subtypes, creating a wrapper
* seems to be the only way to be able to intercept the matcher behavior.
*/
private class InvocationInterceptor
implements IMocksControlState {
/* The wrapped record state */
private RecordState recordState;
/* The modes for the reflection argument matchers */
private ReflectionComparatorMode[] modes;
/**
* Creates an interceptor that will create reflection argument matchers for all arguments of all recorded
* method invocations.
*
* @param modes
* the modes for the reflection argument matchers
*/
public InvocationInterceptor(ReflectionComparatorMode... modes) {
this.modes = modes;
}
/**
* Sets the current wrapped record state.
*
* @param recordState
* the state, not null
*/
public void setRecordState(RecordState recordState) {
this.recordState = recordState;
}
/**
* Overriden to report reflection argument matchers for all arguments of the given method invocation.
*
* @param invocation
* the method invocation, not null
* @return the result of the invocation
*/
@Override
public Object invoke(Invocation invocation) {
LastControl.reportLastControl(LenientMocksControl.this);
createMatchers(invocation);
return recordState.invoke(invocation);
}
/**
* Reports report reflection argument matchers for all arguments of the given method invocation.
* An exception will be thrown if there were already matchers reported for the invocation.
*
* @param invocation
* the method invocation, not null
*/
private void createMatchers(Invocation invocation) {
List matchers = LastControl.pullMatchers();
if (matchers != null && !matchers.isEmpty()) {
if (matchers.size() != invocation.getArguments().length) {
throw new IllegalStateException("This mock control does not support mixing of no-argument matchers and per-argument matchers. "
+ "Either no matchers are defined and the reflection argument matcher is used by default or all matchers are defined explicitly (Eg by using refEq()).");
}
// put all matchers back since pull removes them
for (IArgumentMatcher matcher : matchers) {
LastControl.reportMatcher(matcher);
}
return;
}
Object[] arguments = invocation.getArguments();
if (arguments == null) {
return;
}
for (Object argument : arguments) {
LastControl.reportMatcher(new ReflectionArgumentMatcher<>(argument, modes));
}
}
// Pass through delegation
@Override
public void assertRecordState() {
recordState.assertRecordState();
}
@Override
public void andReturn(Object value) {
recordState.andReturn(value);
}
@Override
public void andThrow(Throwable throwable) {
recordState.andThrow(throwable);
}
@Override
public void andAnswer(IAnswer> answer) {
recordState.andAnswer(answer);
}
@Override
public void andStubReturn(Object value) {
recordState.andStubReturn(value);
}
@Override
public void andStubThrow(Throwable throwable) {
recordState.andStubThrow(throwable);
}
@Override
public void andStubAnswer(IAnswer> answer) {
recordState.andStubAnswer(answer);
}
@Override
public void asStub() {
recordState.asStub();
}
@Override
public void times(Range range) {
recordState.times(range);
}
@Override
public void checkOrder(boolean value) {
recordState.checkOrder(value);
}
@Override
public void replay() {
recordState.replay();
}
@Override
public void verifyRecording() {
recordState.verifyRecording();
}
@Override
public void verifyUnexpectedCalls() {
recordState.verifyUnexpectedCalls();
}
@Override
public void verify() {
recordState.verify();
}
/*
* public void setDefaultReturnValue(Object value) {
* recordState.setDefaultReturnValue(value);
* }
* public void setDefaultThrowable(Throwable throwable) {
* recordState.setDefaultThrowable(throwable);
* }
* public void setDefaultVoidCallable() {
* recordState.setDefaultVoidCallable();
* }
* public void setDefaultMatcher(ArgumentsMatcher matcher) {
* recordState.setDefaultMatcher(matcher);
* }
* public void setMatcher(Method method, ArgumentsMatcher matcher) {
* recordState.setMatcher(method, matcher);
* }
*/
/**
* @see org.easymock.internal.IMocksControlState#andDelegateTo(java.lang.Object)
*/
@Override
public void andDelegateTo(Object value) {
recordState.andDelegateTo(value);
}
/**
* @see org.easymock.internal.IMocksControlState#andVoid()
*/
@Override
public void andVoid() {
recordState.andVoid();
}
/**
* @see org.easymock.internal.IMocksControlState#andStubDelegateTo(java.lang.Object)
*/
@Override
public void andStubDelegateTo(Object value) {
recordState.andStubDelegateTo(value);
}
/**
* @see org.easymock.internal.IMocksControlState#checkIsUsedInOneThread(boolean)
*/
@Override
public void checkIsUsedInOneThread(boolean value) {
recordState.checkIsUsedInOneThread(value);
}
/**
* @see org.easymock.internal.IMocksControlState#makeThreadSafe(boolean)
*/
@Override
public void makeThreadSafe(boolean value) {
recordState.makeThreadSafe(value);
}
}
}