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