1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package net.sf.surrogate.core;
31
32 import java.lang.reflect.AccessibleObject;
33 import java.lang.reflect.Constructor;
34 import java.lang.reflect.Method;
35
36 import com.mockobjects.ExceptionalReturnValue;
37 import com.mockobjects.ExpectationCounter;
38 import com.mockobjects.ExpectationList;
39 import com.mockobjects.ReturnValues;
40
41 /***
42 * General mock class to mock a specific method. Useful to mock "final" methods
43 * where it is impossible to override the actual class with a Mock
44 * implementation.
45 * <p>
46 * Example:
47 * <pre>
48 * SurrogateManager manager = SurrogateManager.getInstance();
49 * manager.reset();
50 * MockMethod mockTime = new MockMethod(java.lang.System.class,"currentTimeMillis");
51 * manager.addMockMethod(mockTime);
52 * mockTime.addReturnValue(new Long(2000));
53 * mockTime.addReturnValue(new Long(4000));
54 * mockTime.setExpectedCalls(4);
55 * ...
56 * ...
57 * mockTime.verify();
58 * </pre>
59 * <p>
60 * Registers a mock method for the <code>java.lang.System.currentTimeMillis</code>
61 * method and specifies that the method should be called exactly four times,
62 * or else an error is generated. The first call returns "2000" and
63 * the next three calls return "4000"
64 *
65 * @see SurrogateManager#addMockMethod(MockMethod)
66 *
67 * @author Per S Hustad
68 *
69 */
70 public class MockMethod implements SurrogateManager.MockExecutor {
71
72 private AccessibleObject method;
73
74 private ExpectationCounter myCalls;
75
76 private ReturnValues myReturnValues;
77
78 private ExpectationList[] myParameterValues;
79
80 private Class[] parameterTypes;
81
82 private boolean isVoid;
83
84 /***
85 * Mocks a method. Useful if it has been impossible to generate a mock
86 * object for a class, e.g. the class contains <code>final</code> methods
87 * or we want to mock a <code>static</code> method.
88 * <p>
89 * Note that for methods to be substituted with their mock implementation,
90 * there must have been defined a <code>pointcut</code> intercepting the
91 * method call or method execution. Otherwise, the mock method will never be
92 * called, even if it has been registered.
93 * See {@link net.sf.surrogate.core.SurrogateCalls} for
94 * pointcut definition details.
95 * <p>
96 *
97 * @param theClass
98 * the class containing the method to mock.
99 * @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, Class[] parameterTypes) throws NoSuchMethodException {
112 Method m = theClass.getDeclaredMethod(method, parameterTypes);
113 isVoid = (m.getReturnType() == Void.TYPE);
114 init(m, m.getParameterTypes());
115 }
116
117 /***
118 * Mocks a method. This method is useful in the case where it has been
119 * impossible to generate a mock object for a class, e.g. the class contains
120 * <code>final</code> methods or we want to mock a <code>static</code>
121 * method.
122 * <p>
123 * Note that for methods to be substituted with their mock implementation,
124 * there must have been defined a <code>pointcut</code> intercepting the
125 * method call or method execution. Otherwise, the mock method will never be
126 * called, even if it has been registered. See {@link SurrogateCalls}for
127 * pointcut definition details.
128 * <p>
129 *
130 * @param theClass
131 * the class containing the method to mock.
132 * @param method
133 * name of the method to mock. Must exist in the class <b>and
134 * only one method with that name must exist </b>. If several
135 * overloaded methods exists with the same name, use
136 * {@link #MockMethod(Class,String,Class[])}
137 * @throws NoSuchMethodException
138 * if the method cannot be found
139 * @throws IllegalArgumentException
140 * if more than one method exists with the name.
141 * @see #MockMethod(Class, String, Class[])
142 * @see #MockMethod(Class, Class[])
143 * @see SurrogateManager#addMockMethod(MockMethod)
144 */
145 public MockMethod(Class theClass, String method) throws NoSuchMethodException {
146 Method m[] = theClass.getDeclaredMethods();
147 int index = -1;
148 int count = 0;
149 for (int i = 0; i < m.length; i++) {
150 if (m[i].getName().equals(method)) {
151 index = i;
152 count++;
153 if (count > 1) {
154 throw new IllegalArgumentException("More than one method:" + theClass.getName() + "." + method);
155 }
156 }
157 }
158 if (index < 0) {
159 throw new NoSuchMethodException(theClass.getName() + "." + method);
160 }
161 isVoid = (m[index].getReturnType() == Void.TYPE);
162 init(m[index], m[index].getParameterTypes());
163 }
164
165 /***
166 * Mocks a constructor.
167 * <p>
168 * This method is useful when it has been impossible to generate a mock
169 * object for a class, e.g. the class is final or the "real" constructor
170 * makes some unwanted procssing.
171 * <p>
172 * Note that for constructors to be substituted with their mock
173 * implementation, there must have been defined a <code>pointcut</code>
174 * intercepting the constructor call or execution. Otherwise, the mock
175 * method will never be called, even if it has been registered. See
176 * {@link SurrogateCalls}for pointcut definition details.
177 * <p>
178 * Note, that for mock constructors, the corresponding <code>pointcut</code>
179 * should be a <b>call </b> type pointcut if the corresponding
180 * <code>MockMethod</code> is to return some values. If the
181 * <code>pointcut</code> is "execution", the return values from the
182 * MockMethod will disappear somewhere in the VM ....
183 *
184 * @param theClass
185 * the class containing the constructor to mock.
186 * @param parameterTypes
187 * the formal parameters to the constructor. E.g. if the
188 * constructor is declared as
189 * <code>Foo(int count,Context ccx,Integer obj)</code> the
190 * formal parameters will be
191 * <code>{Integer.TYPE,Context.class,Integer.class}</code>
192 * @throws NoSuchMethodException
193 * if the constructor cannot be found
194 * @see SurrogateManager#addMockMethod(MockMethod)
195 */
196 public MockMethod(Class theClass, Class[] parameterTypes) throws NoSuchMethodException {
197 Constructor c = theClass.getDeclaredConstructor(parameterTypes);
198 isVoid = false;
199 init(c, c.getParameterTypes());
200 }
201
202 private void init(AccessibleObject accessible, Class[] parameterTypes) {
203 this.method = accessible;
204 this.parameterTypes = parameterTypes;
205 reset();
206 }
207
208 /***
209 * Resets the mock method counters, expectation values and stored actual
210 * values.
211 */
212 public void reset() {
213 myCalls = new ExpectationCounter("Mock" + method.toString());
214 myReturnValues = new ReturnValues("Mock" + method.toString(), true);
215
216 myParameterValues = new ExpectationList[parameterTypes.length];
217 for (int i = 0; i < parameterTypes.length; i++) {
218 myParameterValues[i] = new ExpectationList(method.toString() + " arg" + i + " values");
219 }
220 }
221
222 /***
223 * Sets the number of expected calls to this method. The
224 *
225 * {@link #verify()} method verifies the calls. Note that if you don't call
226 * this method after a reset or creation, the verify method will not
227 * check the actual calls.
228 * @param expectedCalls
229 * the number of expected calls.
230 * @see #verify()
231 */
232 public void setExpectedCalls(int expectedCalls) {
233 myCalls.setExpected(expectedCalls);
234 }
235
236 /***
237 * Sets up the next return value of the mock method. If the method is called
238 * more times than the calls to this method, the last specified return value
239 * is used.
240 *
241 * @param o
242 * the next return value. Primitive types are specified with
243 * their corresponding Object representation, e.g. an "int"
244 * corresponds to "Integer".
245 * @throws IllegalArgumentException
246 * if the underlying AccessibleObject is a method returning
247 * <code>void</code>
248 * @see #addThrowableReturnValue(Throwable)
249 */
250 public void addReturnValue(Object o) {
251 if (isVoid) {
252 throw new IllegalArgumentException("No return type may be specified for \"void\" method");
253 }
254 myReturnValues.add(o);
255
256 }
257
258 /***
259 * Adds an errror to the list of return values.
260 *
261 * @param arg
262 * the throwable to throw on the next call. The mock method will
263 * throw this throwable even if the actual method does not
264 * declare it ...
265 * @see #addReturnValue(Object)
266 */
267 public void addThrowableReturnValue(Throwable arg) {
268 myReturnValues.add(new ExceptionalReturnValue(arg));
269 }
270
271 /***
272 * Sets up the next excpected arguments for the method. The actual arguments
273 * are verified against the expected arguments when the actual method call
274 * is performed. If they mismatch, an Exception will be thrown.
275 *
276 * @param args
277 * array of Object values. <b>NB: </b> must correspond to the
278 * formal parameter types of the method or else a runtime
279 * exception will be thrown. Primitive types should be
280 * encapsulated in their corresponding Object representation,
281 * e.g. int corresponds to Integer
282 * @throws IllegalArgumentException
283 * if the number of aruments in <code>args</code> differs from
284 * the number of arguments in the underlying method or
285 * constructor.
286 */
287 public void addExpectedParameters(Object[] args) {
288 if (args != null && args.length != myParameterValues.length) {
289 throw new IllegalArgumentException(method.toString() + ": got wrong number of parameters");
290 }
291 for (int i = 0; i < args.length; i++) {
292 myParameterValues[i].addExpected(args[i]);
293 }
294 }
295
296 /***
297 * Performs the actual method call. This method should normally not be
298 * called from test harnesses. It is called from the Surrogate core
299 * framework
300 *
301 * @param args
302 * the aruments to the method. Will be compared to the expected
303 * values if such values have been specified.
304 * @return the return value given in {@link #addReturnValue(Object)}unless
305 * the next return value has been set with the
306 * {@link #addThrowableReturnValue(Throwable)}call. In this case,
307 * an Exception is thrown
308 *
309 * @throws Throwable
310 * if an exception has been set up in
311 * {@link #addThrowableReturnValue(Throwable)} or if the number of parameters in
312 * <code>args</code> does not match the number of parameters
313 * in the method signature
314 */
315 public Object execute(Object[] args) throws Throwable {
316 myCalls.inc();
317 if (args != null && args.length != myParameterValues.length) {
318 throw new RuntimeException(method.toString() + ": got wrong number of parameters");
319 }
320 if (args != null) {
321 for (int i = 0; i < args.length; i++) {
322 myParameterValues[i].addActual(args[i]);
323 }
324 }
325 if (myReturnValues.isEmpty() && isVoid) {
326 return null;
327 }
328 Object nextReturnValue = myReturnValues.getNext();
329 if (nextReturnValue instanceof ExceptionalReturnValue) {
330 throw ((ExceptionalReturnValue) nextReturnValue).getException();
331 }
332 return nextReturnValue;
333
334 }
335
336 /***
337 * Gets the java reflection method or constructor
338 *
339 * @return the accessible object as specified in the constructor
340 */
341 /* package protected *//package-summary/html">class="comment"> package protected *//package-summary.html">
342 AccessibleObject getAccessibleObject() {
343 return method;
344 }
345
346 /***
347 * Verifies that the method has been called as many times as specified in
348 * {@link #setExpectedCalls(int)}
349 *
350 * @see #setExpectedCalls(int)
351 */
352 public void verify() {
353 myCalls.verify();
354 }
355
356 /***
357 * Gets a textual representation of this MockMethod
358 *
359 * @return the underlying method or constructor's <code>toString</code>
360 */
361 public String toString() {
362 return method.toString();
363 }
364
365 }