View Javadoc

1   /* Copyright (c) 2005 Per S Hustad. All rights reserved.
2    *
3    * Redistribution and use in source and binary forms, with or without 
4    * modification, are permitted provided that the following conditions are met:
5    *
6    *  o Redistributions of source code must retain the above copyright notice, 
7    *    this list of conditions and the following disclaimer. 
8    *    
9    *  o Redistributions in binary form must reproduce the above copyright notice, 
10   *    this list of conditions and the following disclaimer in the documentation 
11   *    and/or other materials provided with the distribution. 
12   *    
13   *  o Neither the name of surrogate.sourceforge.net nor the names of 
14   *    its contributors may be used to endorse or promote products derived 
15   *    from this software without specific prior written permission. 
16   *    
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
18   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
19   * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
20   * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
21   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
22   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
23   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
24   * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
25   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
26   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
27   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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,&quot;currentTimeMillis&quot;);
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">/* package protected *//package-summary.html">class="comment"> package protected */
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 }