org.unitils.easymock.util.LenientMocksControl Maven / Gradle / Ivy
/*
* 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.lang.reflect.Method;
import java.util.List;
import org.easymock.ArgumentsMatcher;
import org.easymock.IAnswer;
import org.easymock.IArgumentMatcher;
import org.easymock.classextension.internal.MocksClassControl;
import org.easymock.internal.IMocksControlState;
import org.easymock.internal.Invocation;
import org.easymock.internal.LastControl;
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
*/
@SuppressWarnings("deprecation")
public class LenientMocksControl extends MocksClassControl {
/* 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(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(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.
*/
@SuppressWarnings("unchecked")
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
*/
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