001: /*
002: * Copyright 2006-2007, Unitils.org
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.unitils.easymock.util;
017:
018: import java.lang.reflect.Method;
019: import java.util.List;
020:
021: import org.easymock.ArgumentsMatcher;
022: import org.easymock.IAnswer;
023: import org.easymock.IArgumentMatcher;
024: import org.easymock.classextension.internal.MocksClassControl;
025: import org.easymock.internal.IMocksControlState;
026: import org.easymock.internal.Invocation;
027: import org.easymock.internal.LastControl;
028: import org.easymock.internal.Range;
029: import org.easymock.internal.RecordState;
030: import org.unitils.reflectionassert.ReflectionComparatorMode;
031:
032: /**
033: * An EasyMock mock control that uses the reflection argument matcher for all arguments of a method invocation.
034: * <p/>
035: * No explicit argument matcher setting is needed (or allowed). This control will automatically report
036: * lenient reflection argument matchers. These matchers can apply some leniency when comparing expected and actual
037: * argument values.
038: * <p/>
039: * Setting the {@link ReflectionComparatorMode#IGNORE_DEFAULTS} mode will for example ignore all fields that
040: * have default values as expected values. E.g. if a null value is recorded as argument it will not be checked when
041: * the actual invocation occurs. The same applies for inner-fields of object arguments that contain default java values.
042: * <p/>
043: * Setting the {@link ReflectionComparatorMode#LENIENT_DATES} mode will ignore the actual date values of arguments and
044: * inner fields of arguments. It will only check whether both dates are null or both dates are not null. The actual
045: * date and hour do not matter.
046: * <p/>
047: * Setting the {@link ReflectionComparatorMode#LENIENT_ORDER} mode will ignore the actual order of collections and
048: * arrays arguments and inner fields of arguments. It will only check whether they both contain the same elements.
049: *
050: * @author Tim Ducheyne
051: * @author Filip Neven
052: * @see ReflectionComparatorMode
053: * @see org.unitils.reflectionassert.ReflectionComparator
054: */
055: @SuppressWarnings("deprecation")
056: public class LenientMocksControl extends MocksClassControl {
057:
058: /* The interceptor that wraps the record state */
059: private InvocationInterceptor invocationInterceptor;
060:
061: /**
062: * Creates a default (no default returns and no order checking) mock control.
063: *
064: * @param modes the modes for the reflection argument matcher
065: */
066: public LenientMocksControl(ReflectionComparatorMode... modes) {
067: this (MockType.DEFAULT, modes);
068: }
069:
070: /**
071: * Creates a mock control.<ul>
072: * <li>Default mock type: no default return values and no order checking</li>
073: * <li>Nice mock type: returns default values if no return value set, no order checking</li>
074: * <li>Strict mock type: no default return values and strict order checking</li>
075: * </ul>
076: *
077: * @param type the EasyMock mock type
078: * @param modes the modes for the reflection argument matcher
079: */
080: public LenientMocksControl(MockType type,
081: ReflectionComparatorMode... modes) {
082: super (type);
083: this .invocationInterceptor = new InvocationInterceptor(modes);
084: }
085:
086: /**
087: * Overriden to be able to replace the record behavior that going to record all method invocations.
088: * The interceptor will make sure that reflection argument matchers will be reported for the
089: * arguments of all recorded method invocations.
090: *
091: * @return the state, wrapped in case of a RecordState
092: */
093: @Override
094: public IMocksControlState getState() {
095: IMocksControlState mocksControlState = super .getState();
096: if (mocksControlState instanceof RecordState) {
097: invocationInterceptor
098: .setRecordState((RecordState) mocksControlState);
099: return invocationInterceptor;
100: }
101: return mocksControlState;
102: }
103:
104: /**
105: * A wrapper for the record state in easy mock that will intercept the invoke method
106: * so that it can install reflection argument matchers for all arguments of the recorded method invocation.
107: * <p/>
108: * The old easy mock way of having a single argument matcher for all arguments has been deprecated. Since
109: * EasyMock 2 each argument should have its own matcher. We however want to avoid having to set all
110: * matchers to the reflection argument matcher explicitly.
111: * Because some of the methods are declared final and some classes explicitly cast to subtypes, creating a wrapper
112: * seems to be the only way to be able to intercept the matcher behavior.
113: */
114: @SuppressWarnings("unchecked")
115: private class InvocationInterceptor implements IMocksControlState {
116:
117: /* The wrapped record state */
118: private RecordState recordState;
119:
120: /* The modes for the reflection argument matchers */
121: private ReflectionComparatorMode[] modes;
122:
123: /**
124: * Creates an interceptor that will create reflection argument matchers for all arguments of all recorded
125: * method invocations.
126: *
127: * @param modes the modes for the reflection argument matchers
128: */
129: public InvocationInterceptor(ReflectionComparatorMode... modes) {
130: this .modes = modes;
131: }
132:
133: /**
134: * Sets the current wrapped record state.
135: *
136: * @param recordState the state, not null
137: */
138: public void setRecordState(RecordState recordState) {
139: this .recordState = recordState;
140: }
141:
142: /**
143: * Overriden to report reflection argument matchers for all arguments of the given method invocation.
144: *
145: * @param invocation the method invocation, not null
146: * @return the result of the invocation
147: */
148: public Object invoke(Invocation invocation) {
149: LastControl.reportLastControl(LenientMocksControl.this );
150: createMatchers(invocation);
151: return recordState.invoke(invocation);
152: }
153:
154: /**
155: * Reports report reflection argument matchers for all arguments of the given method invocation.
156: * An exception will be thrown if there were already matchers reported for the invocation.
157: *
158: * @param invocation the method invocation, not null
159: */
160: private void createMatchers(Invocation invocation) {
161: List<IArgumentMatcher> matchers = LastControl
162: .pullMatchers();
163: if (matchers != null && !matchers.isEmpty()) {
164: if (matchers.size() != invocation.getArguments().length) {
165: throw new IllegalStateException(
166: "This mock control does not support mixing of no-argument matchers and per-argument matchers. "
167: + "Either no matchers are defined and the reflection argument matcher is used by default or all matchers are defined explicitly (Eg by using refEq()).");
168: }
169: // put all matchers back since pull removes them
170: for (IArgumentMatcher matcher : matchers) {
171: LastControl.reportMatcher(matcher);
172: }
173: return;
174: }
175: Object[] arguments = invocation.getArguments();
176: if (arguments == null) {
177: return;
178: }
179:
180: for (Object argument : arguments) {
181: LastControl
182: .reportMatcher(new ReflectionArgumentMatcher<Object>(
183: argument, modes));
184: }
185: }
186:
187: // Pass through delegation
188:
189: public void assertRecordState() {
190: recordState.assertRecordState();
191: }
192:
193: public void andReturn(Object value) {
194: recordState.andReturn(value);
195: }
196:
197: public void andThrow(Throwable throwable) {
198: recordState.andThrow(throwable);
199: }
200:
201: public void andAnswer(IAnswer answer) {
202: recordState.andAnswer(answer);
203: }
204:
205: public void andStubReturn(Object value) {
206: recordState.andStubReturn(value);
207: }
208:
209: public void andStubThrow(Throwable throwable) {
210: recordState.andStubThrow(throwable);
211: }
212:
213: public void andStubAnswer(IAnswer answer) {
214: recordState.andStubAnswer(answer);
215: }
216:
217: public void asStub() {
218: recordState.asStub();
219: }
220:
221: public void times(Range range) {
222: recordState.times(range);
223: }
224:
225: public void checkOrder(boolean value) {
226: recordState.checkOrder(value);
227: }
228:
229: public void replay() {
230: recordState.replay();
231: }
232:
233: public void verify() {
234: recordState.verify();
235: }
236:
237: public void setDefaultReturnValue(Object value) {
238: recordState.setDefaultReturnValue(value);
239: }
240:
241: public void setDefaultThrowable(Throwable throwable) {
242: recordState.setDefaultThrowable(throwable);
243: }
244:
245: public void setDefaultVoidCallable() {
246: recordState.setDefaultVoidCallable();
247: }
248:
249: public void setDefaultMatcher(ArgumentsMatcher matcher) {
250: recordState.setDefaultMatcher(matcher);
251: }
252:
253: public void setMatcher(Method method, ArgumentsMatcher matcher) {
254: recordState.setMatcher(method, matcher);
255: }
256: }
257:
258: }
|