• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.dx.mockito.inline.extended;
18 
19 import org.mockito.InOrder;
20 import org.mockito.MockSettings;
21 import org.mockito.Mockito;
22 import org.mockito.internal.matchers.LocalizedMatcher;
23 import org.mockito.internal.progress.ArgumentMatcherStorageImpl;
24 import org.mockito.stubbing.Answer;
25 import org.mockito.verification.VerificationMode;
26 
27 import java.lang.reflect.Field;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.List;
32 
33 import static com.android.dx.mockito.inline.InlineDexmakerMockMaker.onSpyInProgressInstance;
34 import static com.android.dx.mockito.inline.InlineStaticMockMaker.onMethodCallDuringVerification;
35 import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
36 
37 /**
38  * Mockito extended with the ability to stub static methods.
39  * <p>E.g.
40  * <pre>
41  *     private class C {
42  *         static int staticMethod(String arg) {
43  *             return 23;
44  *         }
45  *     }
46  *
47  *    {@literal @}Test
48  *     public void test() {
49  *         // static mocking
50  *         MockitoSession session = mockitoSession().staticSpy(C.class).startMocking();
51  *         try {
52  *             doReturn(42).when(() -> {return C.staticMethod(eq("Arg"));});
53  *             assertEquals(42, C.staticMethod("Arg"));
54  *             verify(() -> C.staticMethod(eq("Arg"));
55  *         } finally {
56  *             session.finishMocking();
57  *         }
58  *     }
59  * </pre>
60  * <p>It is possible to use this class for instance mocking too. Hence you can use it as a full
61  * replacement for {@link Mockito}.
62  * <p>This is a prototype that is intended to eventually be upstreamed into mockito proper. Some
63  * APIs might change. All such APIs are annotated with {@link UnstableApi}.
64  */
65 @UnstableApi
66 public class ExtendedMockito extends Mockito {
67     /**
68      * Currently active {@link #mockitoSession() sessions}
69      */
70     private static ArrayList<StaticMockitoSession> sessions = new ArrayList<>();
71 
72     /**
73      * Same as {@link Mockito#doAnswer(Answer)} but adds the ability to stub static method calls via
74      * {@link StaticCapableStubber#when(MockedMethod)} and
75      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
76      */
doAnswer(Answer answer)77     public static StaticCapableStubber doAnswer(Answer answer) {
78         return new StaticCapableStubber(Mockito.doAnswer(answer));
79     }
80 
81     /**
82      * Same as {@link Mockito#doCallRealMethod()} but adds the ability to stub static method calls
83      * via {@link StaticCapableStubber#when(MockedMethod)} and
84      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
85      */
doCallRealMethod()86     public static StaticCapableStubber doCallRealMethod() {
87         return new StaticCapableStubber(Mockito.doCallRealMethod());
88     }
89 
90     /**
91      * Same as {@link Mockito#doNothing()} but adds the ability to stub static method calls via
92      * {@link StaticCapableStubber#when(MockedMethod)} and
93      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
94      */
doNothing()95     public static StaticCapableStubber doNothing() {
96         return new StaticCapableStubber(Mockito.doNothing());
97     }
98 
99     /**
100      * Same as {@link Mockito#doReturn(Object)} but adds the ability to stub static method calls
101      * via {@link StaticCapableStubber#when(MockedMethod)} and
102      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
103      */
doReturn(Object toBeReturned)104     public static StaticCapableStubber doReturn(Object toBeReturned) {
105         return new StaticCapableStubber(Mockito.doReturn(toBeReturned));
106     }
107 
108     /**
109      * Same as {@link Mockito#doReturn(Object, Object...)} but adds the ability to stub static
110      * method calls via {@link StaticCapableStubber#when(MockedMethod)} and
111      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
112      */
doReturn(Object toBeReturned, Object... toBeReturnedNext)113     public static StaticCapableStubber doReturn(Object toBeReturned, Object... toBeReturnedNext) {
114         return new StaticCapableStubber(Mockito.doReturn(toBeReturned, toBeReturnedNext));
115     }
116 
117     /**
118      * Same as {@link Mockito#doThrow(Class)} but adds the ability to stub static method calls via
119      * {@link StaticCapableStubber#when(MockedMethod)} and
120      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
121      */
doThrow(Class<? extends Throwable> toBeThrown)122     public static StaticCapableStubber doThrow(Class<? extends Throwable> toBeThrown) {
123         return new StaticCapableStubber(Mockito.doThrow(toBeThrown));
124     }
125 
126     /**
127      * Same as {@link Mockito#doThrow(Class, Class...)} but adds the ability to stub static method
128      * calls via {@link StaticCapableStubber#when(MockedMethod)} and
129      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
130      */
131     @SafeVarargs
doThrow(Class<? extends Throwable> toBeThrown, Class<? extends Throwable>... toBeThrownNext)132     public static StaticCapableStubber doThrow(Class<? extends Throwable> toBeThrown,
133                                                Class<? extends Throwable>... toBeThrownNext) {
134         return new StaticCapableStubber(Mockito.doThrow(toBeThrown, toBeThrownNext));
135     }
136 
137     /**
138      * Same as {@link Mockito#doThrow(Throwable...)} but adds the ability to stub static method
139      * calls via {@link StaticCapableStubber#when(MockedMethod)} and
140      * {@link StaticCapableStubber#when(MockedVoidMethod)}.
141      */
doThrow(Throwable... toBeThrown)142     public static StaticCapableStubber doThrow(Throwable... toBeThrown) {
143         return new StaticCapableStubber(Mockito.doThrow(toBeThrown));
144     }
145 
146     /**
147      * Many methods of mockito take mock objects. To be able to call the same methods for static
148      * mocking, this method gets a marker object that can be used instead.
149      *
150      * @param clazz The class object the marker should be crated for
151      * @return A marker object. This should not be used directly. It can only be passed into other
152      * ExtendedMockito methods.
153      * @see #inOrder(Object...)
154      * @see #clearInvocations(Object...)
155      * @see #ignoreStubs(Object...)
156      * @see #mockingDetails(Object)
157      * @see #reset(Object[])
158      * @see #verifyNoMoreInteractions(Object...)
159      * @see #verifyZeroInteractions(Object...)
160      */
161     @UnstableApi
162     @SuppressWarnings("unchecked")
staticMockMarker(Class<T> clazz)163     public static <T> T staticMockMarker(Class<T> clazz) {
164         for (StaticMockitoSession session : sessions) {
165             T marker = session.staticMockMarker(clazz);
166 
167             if (marker != null) {
168                 return marker;
169             }
170         }
171         return null;
172     }
173 
174     /**
175      * Same as {@link #staticMockMarker(Class)} but for multiple classes at once.
176      */
177     @UnstableApi
staticMockMarker(Class<?>.... clazz)178     public static Object[] staticMockMarker(Class<?>... clazz) {
179         Object[] markers = new Object[clazz.length];
180 
181         for (int i = 0; i < clazz.length; i++) {
182             for (StaticMockitoSession session : sessions) {
183                 markers[i] = session.staticMockMarker(clazz[i]);
184 
185                 if (markers[i] != null) {
186                     break;
187                 }
188             }
189 
190             if (markers[i] == null) {
191                 return null;
192             }
193         }
194 
195         return markers;
196     }
197 
198     /**
199      * Make an existing object a spy.
200      *
201      * <p>This does <u>not</u> clone the existing objects. If a method is stubbed on a spy
202      * converted by this method all references to the already existing object will be affected by
203      * the stubbing.
204      *
205      * @param toSpy The existing object to convert into a spy
206      */
207     @UnstableApi
208     @SuppressWarnings("CheckReturnValue")
spyOn(Object toSpy)209     public static void spyOn(Object toSpy) {
210         if (onSpyInProgressInstance.get() != null) {
211             throw new IllegalStateException("Cannot set up spying on an existing object while "
212                     + "setting up spying for another existing object");
213         }
214 
215         onSpyInProgressInstance.set(toSpy);
216         try {
217             spy(toSpy);
218         } finally {
219             onSpyInProgressInstance.remove();
220         }
221     }
222 
223     /**
224      * To be used for static mocks/spies in place of {@link Mockito#verify(Object)} when calling
225      * void methods.
226      * <p>E.g.
227      * <pre>
228      *     private class C {
229      *         void instanceMethod(String arg) {}
230      *         static void staticMethod(String arg) {}
231      *     }
232      *
233      *    {@literal @}Test
234      *     public void test() {
235      *         // instance mocking
236      *         C mock = mock(C.class);
237      *         mock.instanceMethod("Hello");
238      *         verify(mock).mockedVoidInstanceMethod(eq("Hello"));
239      *
240      *         // static mocking
241      *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
242      *         C.staticMethod("World");
243      *         verify(() -> C.staticMethod(eq("World"));
244      *         session.finishMocking();
245      *     }
246      * </pre>
247      */
verify(MockedVoidMethod method)248     public static void verify(MockedVoidMethod method) {
249         verify(method, times(1));
250     }
251 
252     /**
253      * To be used for static mocks/spies in place of {@link Mockito#verify(Object)}.
254      * <p>E.g. (please notice the 'return' in the lambda when verifying the static call)
255      * <pre>
256      *     private class C {
257      *         int instanceMethod(String arg) {
258      *             return 1;
259      *         }
260      *
261      *         int static staticMethod(String arg) {
262      *             return 2;
263      *         }
264      *     }
265      *
266      *    {@literal @}Test
267      *     public void test() {
268      *         // instance mocking
269      *         C mock = mock(C.class);
270      *         mock.instanceMethod("Hello");
271      *         verify(mock).mockedVoidInstanceMethod(eq("Hello"));
272      *
273      *         // static mocking
274      *         MockitoSession session = mockitoSession().staticMock(C.class).startMocking();
275      *         C.staticMethod("World");
276      *         verify(() -> <b>{return</b> C.staticMethod(eq("World")<b>;}</b>);
277      *         session.finishMocking();
278      *     }
279      * </pre>
280      */
281     @UnstableApi
verify(MockedMethod method)282     public static void verify(MockedMethod method) {
283         verify(method, times(1));
284     }
285 
286     /**
287      * To be used for static mocks/spies in place of
288      * {@link Mockito#verify(Object, VerificationMode)} when calling void methods.
289      *
290      * @see #verify(MockedVoidMethod)
291      */
292     @UnstableApi
verify(MockedVoidMethod method, VerificationMode mode)293     public static void verify(MockedVoidMethod method, VerificationMode mode) {
294         verifyInt(method, mode, null);
295     }
296 
297     /**
298      * To be used for static mocks/spies in place of
299      * {@link Mockito#verify(Object, VerificationMode)}.
300      *
301      * @see #verify(MockedMethod)
302      */
303     @UnstableApi
verify(MockedMethod method, VerificationMode mode)304     public static void verify(MockedMethod method, VerificationMode mode) {
305         verify((MockedVoidMethod) method::get, mode);
306     }
307 
308     /**
309      * Same as {@link Mockito#inOrder(Object...)} but adds the ability to verify static method
310      * calls via {@link StaticInOrder#verify(MockedMethod)},
311      * {@link StaticInOrder#verify(MockedVoidMethod)},
312      * {@link StaticInOrder#verify(MockedMethod, VerificationMode)}, and
313      * {@link StaticInOrder#verify(MockedVoidMethod, VerificationMode)}.
314      * <p>To verify static method calls, the result of {@link #staticMockMarker(Class)} has to be
315      * passed to the {@code mocksAndMarkers} parameter. It is possible to mix static and instance
316      * mocking.
317      */
318     @UnstableApi
inOrder(Object... mocksAndMarkers)319     public static StaticInOrder inOrder(Object... mocksAndMarkers) {
320         return new StaticInOrder(Mockito.inOrder(mocksAndMarkers));
321     }
322 
323     /**
324      * Same as {@link Mockito#mockitoSession()} but adds the ability to mock static methods
325      * calls via {@link StaticMockitoSessionBuilder#mockStatic(Class)},
326      * {@link StaticMockitoSessionBuilder#mockStatic(Class, Answer)}, and {@link
327      * StaticMockitoSessionBuilder#mockStatic(Class, MockSettings)};
328      * <p>All mocking spying will be removed once the session is finished.
329      */
mockitoSession()330     public static StaticMockitoSessionBuilder mockitoSession() {
331         return new StaticMockitoSessionBuilder(Mockito.mockitoSession());
332     }
333 
334     /**
335      * Common implementation of verification of static method calls.
336      *
337      * @param method          The static method call to be verified
338      * @param mode            The verification mode
339      * @param instanceInOrder If set, the {@link StaticInOrder} object
340      */
341     @SuppressWarnings({"CheckReturnValue", "MockitoUsage", "unchecked"})
verifyInt(MockedVoidMethod method, VerificationMode mode, InOrder instanceInOrder)342     static void verifyInt(MockedVoidMethod method, VerificationMode mode, InOrder
343             instanceInOrder) {
344         if (onMethodCallDuringVerification.get() != null) {
345             throw new IllegalStateException("Verification is already in progress on this "
346                     + "thread.");
347         }
348 
349         ArrayList<Method> verifications = new ArrayList<>();
350 
351         /* Set up callback that is triggered when the next static method is called on this thread.
352          *
353          * This is necessary as we don't know which class the method will be called on. Once the
354          * call is intercepted this will
355          *    1. Remove all matchers (e.g. eq(), any()) from the matcher stack
356          *    2. Call verify on the marker for the class
357          *    3. Add the markers back to the stack
358          */
359         onMethodCallDuringVerification.set((clazz, verifiedMethod) -> {
360             // TODO: O holy reflection! Let's hope we can integrate this better.
361             try {
362                 ArgumentMatcherStorageImpl argMatcherStorage = (ArgumentMatcherStorageImpl)
363                         mockingProgress().getArgumentMatcherStorage();
364                 List<LocalizedMatcher> matchers;
365 
366                 // Matcher are called before verify, hence remove the from the storage
367                 Method resetStackMethod
368                         = argMatcherStorage.getClass().getDeclaredMethod("resetStack");
369                 resetStackMethod.setAccessible(true);
370 
371                 matchers = (List<LocalizedMatcher>) resetStackMethod.invoke(argMatcherStorage);
372 
373                 if (instanceInOrder == null) {
374                     verify(staticMockMarker(clazz), mode);
375                 } else {
376                     instanceInOrder.verify(staticMockMarker(clazz), mode);
377                 }
378 
379                 // Add the matchers back after verify is called
380                 Field matcherStackField
381                         = argMatcherStorage.getClass().getDeclaredField("matcherStack");
382                 matcherStackField.setAccessible(true);
383 
384                 Method pushMethod = matcherStackField.getType().getDeclaredMethod("push",
385                         Object.class);
386 
387                 for (LocalizedMatcher matcher : matchers) {
388                     pushMethod.invoke(matcherStackField.get(argMatcherStorage), matcher);
389                 }
390             } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException
391                     | InvocationTargetException | ClassCastException e) {
392                 throw new Error("Reflection failed. Do you use a compatible version of "
393                         + "mockito?", e);
394             }
395 
396             verifications.add(verifiedMethod);
397         });
398         try {
399             try {
400                 // Trigger the method call. This call will be intercepted and trigger the
401                 // onMethodCallDuringVerification callback.
402                 method.run();
403             } catch (Throwable t) {
404                 if (t instanceof RuntimeException) {
405                     throw (RuntimeException) t;
406                 } else if (t instanceof Error) {
407                     throw (Error) t;
408                 }
409                 throw new RuntimeException(t);
410             }
411 
412             if (verifications.isEmpty()) {
413                 // Make sure something was intercepted
414                 throw new IllegalArgumentException("Nothing was verified. Does the lambda call "
415                         + "a static method on a 'static' mock/spy ?");
416             } else if (verifications.size() > 1) {
417                 // A lambda might call several methods. In this case it is not clear what should
418                 // be verified. Hence throw an error.
419                 throw new IllegalArgumentException("Multiple intercepted calls on methods "
420                         + verifications);
421             }
422         } finally {
423             onMethodCallDuringVerification.remove();
424         }
425     }
426 
427     /**
428      * Register a new session.
429      *
430      * @param session Session to register
431      */
addSession(StaticMockitoSession session)432     static void addSession(StaticMockitoSession session) {
433         sessions.add(session);
434     }
435 
436     /**
437      * Remove a finished session.
438      *
439      * @param session Session to remove
440      */
removeSession(StaticMockitoSession session)441     static void removeSession(StaticMockitoSession session) {
442         sessions.remove(session);
443     }
444 }
445