001: /* Copyright (c) 2005 Per S Hustad. All rights reserved.
002: *
003: * Redistribution and use in source and binary forms, with or without
004: * modification, are permitted provided that the following conditions are met:
005: *
006: * o Redistributions of source code must retain the above copyright notice,
007: * this list of conditions and the following disclaimer.
008: *
009: * o Redistributions in binary form must reproduce the above copyright notice,
010: * this list of conditions and the following disclaimer in the documentation
011: * and/or other materials provided with the distribution.
012: *
013: * o Neither the name of surrogate.sourceforge.net nor the names of
014: * its contributors may be used to endorse or promote products derived
015: * from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
019: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
020: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
024: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
025: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
026: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
027: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: */
029:
030: package net.sf.surrogate.core;
031:
032: import java.lang.reflect.AccessibleObject;
033: import java.lang.reflect.Constructor;
034: import java.lang.reflect.Method;
035:
036: import com.mockobjects.ExceptionalReturnValue;
037: import com.mockobjects.ExpectationCounter;
038: import com.mockobjects.ExpectationList;
039: import com.mockobjects.ReturnValues;
040:
041: /**
042: * General mock class to mock a specific method. Useful to mock "final" methods
043: * where it is impossible to override the actual class with a Mock
044: * implementation.
045: * <p>
046: * Example:
047: * <pre>
048: * SurrogateManager manager = SurrogateManager.getInstance();
049: * manager.reset();
050: * MockMethod mockTime = new MockMethod(java.lang.System.class,"currentTimeMillis");
051: * manager.addMockMethod(mockTime);
052: * mockTime.addReturnValue(new Long(2000));
053: * mockTime.addReturnValue(new Long(4000));
054: * mockTime.setExpectedCalls(4);
055: * ...
056: * ...
057: * mockTime.verify();
058: * </pre>
059: * <p>
060: * Registers a mock method for the <code>java.lang.System.currentTimeMillis</code>
061: * method and specifies that the method should be called exactly four times,
062: * or else an error is generated. The first call returns "2000" and
063: * the next three calls return "4000"
064: *
065: * @see SurrogateManager#addMockMethod(MockMethod)
066: *
067: * @author Per S Hustad
068: *
069: */
070: public class MockMethod implements SurrogateManager.MockExecutor {
071:
072: private AccessibleObject method;
073:
074: private ExpectationCounter myCalls;
075:
076: private ReturnValues myReturnValues;
077:
078: private ExpectationList[] myParameterValues;
079:
080: private Class[] parameterTypes;
081:
082: private boolean isVoid;
083:
084: /**
085: * Mocks a method. Useful if it has been impossible to generate a mock
086: * object for a class, e.g. the class contains <code>final</code> methods
087: * or we want to mock a <code>static</code> method.
088: * <p>
089: * Note that for methods to be substituted with their mock implementation,
090: * there must have been defined a <code>pointcut</code> intercepting the
091: * method call or method execution. Otherwise, the mock method will never be
092: * called, even if it has been registered.
093: * See {@link net.sf.surrogate.core.SurrogateCalls} for
094: * pointcut definition details.
095: * <p>
096: *
097: * @param theClass
098: * the class containing the method to mock.
099: * @param method
100: * name of the method to mock. Must exist in the class.
101: * @param parameterTypes
102: * the formal parameters to the method. E.g. if the method is
103: * declared as
104: * <code>int foo(int count,Context ccx,Integer obj)</code> the
105: * formal parameters will be
106: * <code>{Integer.TYPE,Context.class,Integer.class}</code>
107: * @throws NoSuchMethodException
108: * if the method cannot be found
109: * @see SurrogateManager#addMockMethod(MockMethod)
110: */
111: public MockMethod(Class theClass, String method,
112: Class[] parameterTypes) throws NoSuchMethodException {
113: Method m = theClass.getDeclaredMethod(method, parameterTypes);
114: isVoid = (m.getReturnType() == Void.TYPE);
115: init(m, m.getParameterTypes());
116: }
117:
118: /**
119: * Mocks a method. This method is useful in the case where it has been
120: * impossible to generate a mock object for a class, e.g. the class contains
121: * <code>final</code> methods or we want to mock a <code>static</code>
122: * method.
123: * <p>
124: * Note that for methods to be substituted with their mock implementation,
125: * there must have been defined a <code>pointcut</code> intercepting the
126: * method call or method execution. Otherwise, the mock method will never be
127: * called, even if it has been registered. See {@link SurrogateCalls}for
128: * pointcut definition details.
129: * <p>
130: *
131: * @param theClass
132: * the class containing the method to mock.
133: * @param method
134: * name of the method to mock. Must exist in the class <b>and
135: * only one method with that name must exist </b>. If several
136: * overloaded methods exists with the same name, use
137: * {@link #MockMethod(Class,String,Class[])}
138: * @throws NoSuchMethodException
139: * if the method cannot be found
140: * @throws IllegalArgumentException
141: * if more than one method exists with the name.
142: * @see #MockMethod(Class, String, Class[])
143: * @see #MockMethod(Class, Class[])
144: * @see SurrogateManager#addMockMethod(MockMethod)
145: */
146: public MockMethod(Class theClass, String method)
147: throws NoSuchMethodException {
148: Method m[] = theClass.getDeclaredMethods();
149: int index = -1;
150: int count = 0;
151: for (int i = 0; i < m.length; i++) {
152: if (m[i].getName().equals(method)) {
153: index = i;
154: count++;
155: if (count > 1) {
156: throw new IllegalArgumentException(
157: "More than one method:"
158: + theClass.getName() + "." + method);
159: }
160: }
161: }
162: if (index < 0) {
163: throw new NoSuchMethodException(theClass.getName() + "."
164: + method);
165: }
166: isVoid = (m[index].getReturnType() == Void.TYPE);
167: init(m[index], m[index].getParameterTypes());
168: }
169:
170: /**
171: * Mocks a constructor.
172: * <p>
173: * This method is useful when it has been impossible to generate a mock
174: * object for a class, e.g. the class is final or the "real" constructor
175: * makes some unwanted procssing.
176: * <p>
177: * Note that for constructors to be substituted with their mock
178: * implementation, there must have been defined a <code>pointcut</code>
179: * intercepting the constructor call or execution. Otherwise, the mock
180: * method will never be called, even if it has been registered. See
181: * {@link SurrogateCalls}for pointcut definition details.
182: * <p>
183: * Note, that for mock constructors, the corresponding <code>pointcut</code>
184: * should be a <b>call </b> type pointcut if the corresponding
185: * <code>MockMethod</code> is to return some values. If the
186: * <code>pointcut</code> is "execution", the return values from the
187: * MockMethod will disappear somewhere in the VM ....
188: *
189: * @param theClass
190: * the class containing the constructor to mock.
191: * @param parameterTypes
192: * the formal parameters to the constructor. E.g. if the
193: * constructor is declared as
194: * <code>Foo(int count,Context ccx,Integer obj)</code> the
195: * formal parameters will be
196: * <code>{Integer.TYPE,Context.class,Integer.class}</code>
197: * @throws NoSuchMethodException
198: * if the constructor cannot be found
199: * @see SurrogateManager#addMockMethod(MockMethod)
200: */
201: public MockMethod(Class theClass, Class[] parameterTypes)
202: throws NoSuchMethodException {
203: Constructor c = theClass.getDeclaredConstructor(parameterTypes);
204: isVoid = false;
205: init(c, c.getParameterTypes());
206: }
207:
208: private void init(AccessibleObject accessible,
209: Class[] parameterTypes) {
210: this .method = accessible;
211: this .parameterTypes = parameterTypes;
212: reset();
213: }
214:
215: /**
216: * Resets the mock method counters, expectation values and stored actual
217: * values.
218: */
219: public void reset() {
220: myCalls = new ExpectationCounter("Mock" + method.toString());
221: myReturnValues = new ReturnValues("Mock" + method.toString(),
222: true);
223:
224: myParameterValues = new ExpectationList[parameterTypes.length];
225: for (int i = 0; i < parameterTypes.length; i++) {
226: myParameterValues[i] = new ExpectationList(method
227: .toString()
228: + " arg" + i + " values");
229: }
230: }
231:
232: /**
233: * Sets the number of expected calls to this method. The
234: *
235: * {@link #verify()} method verifies the calls. Note that if you don't call
236: * this method after a reset or creation, the verify method will not
237: * check the actual calls.
238: * @param expectedCalls
239: * the number of expected calls.
240: * @see #verify()
241: */
242: public void setExpectedCalls(int expectedCalls) {
243: myCalls.setExpected(expectedCalls);
244: }
245:
246: /**
247: * Sets up the next return value of the mock method. If the method is called
248: * more times than the calls to this method, the last specified return value
249: * is used.
250: *
251: * @param o
252: * the next return value. Primitive types are specified with
253: * their corresponding Object representation, e.g. an "int"
254: * corresponds to "Integer".
255: * @throws IllegalArgumentException
256: * if the underlying AccessibleObject is a method returning
257: * <code>void</code>
258: * @see #addThrowableReturnValue(Throwable)
259: */
260: public void addReturnValue(Object o) {
261: if (isVoid) {
262: throw new IllegalArgumentException(
263: "No return type may be specified for \"void\" method");
264: }
265: myReturnValues.add(o);
266:
267: }
268:
269: /**
270: * Adds an errror to the list of return values.
271: *
272: * @param arg
273: * the throwable to throw on the next call. The mock method will
274: * throw this throwable even if the actual method does not
275: * declare it ...
276: * @see #addReturnValue(Object)
277: */
278: public void addThrowableReturnValue(Throwable arg) {
279: myReturnValues.add(new ExceptionalReturnValue(arg));
280: }
281:
282: /**
283: * Sets up the next excpected arguments for the method. The actual arguments
284: * are verified against the expected arguments when the actual method call
285: * is performed. If they mismatch, an Exception will be thrown.
286: *
287: * @param args
288: * array of Object values. <b>NB: </b> must correspond to the
289: * formal parameter types of the method or else a runtime
290: * exception will be thrown. Primitive types should be
291: * encapsulated in their corresponding Object representation,
292: * e.g. int corresponds to Integer
293: * @throws IllegalArgumentException
294: * if the number of aruments in <code>args</code> differs from
295: * the number of arguments in the underlying method or
296: * constructor.
297: */
298: public void addExpectedParameters(Object[] args) {
299: if (args != null && args.length != myParameterValues.length) {
300: throw new IllegalArgumentException(method.toString()
301: + ": got wrong number of parameters");
302: }
303: for (int i = 0; i < args.length; i++) {
304: myParameterValues[i].addExpected(args[i]);
305: }
306: }
307:
308: /**
309: * Performs the actual method call. This method should normally not be
310: * called from test harnesses. It is called from the Surrogate core
311: * framework
312: *
313: * @param args
314: * the aruments to the method. Will be compared to the expected
315: * values if such values have been specified.
316: * @return the return value given in {@link #addReturnValue(Object)}unless
317: * the next return value has been set with the
318: * {@link #addThrowableReturnValue(Throwable)}call. In this case,
319: * an Exception is thrown
320: *
321: * @throws Throwable
322: * if an exception has been set up in
323: * {@link #addThrowableReturnValue(Throwable)} or if the number of parameters in
324: * <code>args</code> does not match the number of parameters
325: * in the method signature
326: */
327: public Object execute(Object[] args) throws Throwable {
328: myCalls.inc();
329: if (args != null && args.length != myParameterValues.length) {
330: throw new RuntimeException(method.toString()
331: + ": got wrong number of parameters");
332: }
333: if (args != null) {
334: for (int i = 0; i < args.length; i++) {
335: myParameterValues[i].addActual(args[i]);
336: }
337: }
338: if (myReturnValues.isEmpty() && isVoid) {
339: return null;
340: }
341: Object nextReturnValue = myReturnValues.getNext();
342: if (nextReturnValue instanceof ExceptionalReturnValue) {
343: throw ((ExceptionalReturnValue) nextReturnValue)
344: .getException();
345: }
346: return nextReturnValue;
347:
348: }
349:
350: /**
351: * Gets the java reflection method or constructor
352: *
353: * @return the accessible object as specified in the constructor
354: */
355: /* package protected */
356: AccessibleObject getAccessibleObject() {
357: return method;
358: }
359:
360: /**
361: * Verifies that the method has been called as many times as specified in
362: * {@link #setExpectedCalls(int)}
363: *
364: * @see #setExpectedCalls(int)
365: */
366: public void verify() {
367: myCalls.verify();
368: }
369:
370: /**
371: * Gets a textual representation of this MockMethod
372: *
373: * @return the underlying method or constructor's <code>toString</code>
374: */
375: public String toString() {
376: return method.toString();
377: }
378:
379: }
|