View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit;
6   
7   import static java.lang.reflect.Modifier.isAbstract;
8   
9   import static mockit.internal.util.GeneratedClasses.isGeneratedImplementationClassName;
10  
11  import edu.umd.cs.findbugs.annotations.NonNull;
12  import edu.umd.cs.findbugs.annotations.Nullable;
13  
14  import java.lang.reflect.ParameterizedType;
15  import java.lang.reflect.Proxy;
16  import java.lang.reflect.Type;
17  import java.lang.reflect.TypeVariable;
18  
19  import mockit.internal.classGeneration.ConcreteSubclass;
20  import mockit.internal.faking.CaptureOfFakedImplementations;
21  import mockit.internal.faking.FakeClassSetup;
22  import mockit.internal.faking.FakeClasses;
23  import mockit.internal.faking.FakedImplementationClass;
24  import mockit.internal.reflection.ConstructorReflection;
25  import mockit.internal.reflection.MockInvocationHandler;
26  import mockit.internal.startup.Startup;
27  import mockit.internal.state.TestRun;
28  
29  /**
30   * A base class used in the creation of a <em>fake</em> for an <em>external</em> type, which is usually a class from
31   * some library or component used from the <em>internal</em> codebase of the system under test (SUT). Such fake classes
32   * can be used as <em>fake implementations</em> for use in unit or integration tests. For example:
33   *
34   * <pre>{@code
35   * public final class FakeSystem <strong>extends MockUp&lt;System></strong> {
36   *    <strong>&#64;Mock</strong> public static long nanoTime() { return 123L; }
37   * }
38   * }</pre>
39   *
40   * One or more <em>fake methods</em> annotated {@linkplain Mock as such} must be defined in the concrete subclass. Each
41   * <code>@Mock</code> method should have a matching method or constructor in the faked class. At runtime, the execution
42   * of a faked method/constructor will get redirected to the corresponding fake method.
43   * <p>
44   * When the type to be faked is specified indirectly through a {@linkplain TypeVariable type variable}, then that type
45   * is taken as a <em>base</em> type whose concrete implementation classes should <em>also</em> get faked. Example:
46   *
47   * <pre>{@code
48   * &#64;Test
49   * public &lt;<strong>BC extends SomeBaseClass</strong>> void someTest() {
50   *     new MockUp&lt;<strong>BC</strong>>() {
51   *        &#64;Mock int someMethod(int i) { return i + 1; }
52   *     };
53   *
54   *     int i = new AConcreteSubclass().someMethod(1);
55   *     assertEquals(2, i);
56   * }
57   * }</pre>
58   *
59   * @param <T>
60   *            specifies the type to be faked; if a type variable is used, then all implementation classes extending or
61   *            implementing that base type are also faked; if the type argument itself is a parameterized type, then only
62   *            its raw type is considered
63   *
64   * @see #MockUp()
65   * @see #MockUp(Class)
66   * @see #MockUp(Object)
67   * @see #getMockInstance()
68   * @see #onTearDown()
69   * @see #targetType
70   * @see <a href="http://jmockit.github.io/tutorial/Faking.html#setUp" target="tutorial">Tutorial</a>
71   */
72  public abstract class MockUp<T> {
73      static {
74          Startup.verifyInitialization();
75      }
76  
77      /**
78       * Holds the class or generic type targeted by this fake instance.
79       */
80      protected final Type targetType;
81  
82      @Nullable
83      private final Class<?> mockedClass;
84      @Nullable
85      private T mockInstance;
86      @Nullable
87      private T invokedInstance;
88  
89      /**
90       * Applies the {@linkplain Mock fake methods} defined in the concrete subclass to the class or interface specified
91       * through the type parameter.
92       *
93       * @see #MockUp(Class)
94       * @see #MockUp(Object)
95       */
96      protected MockUp() {
97          MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();
98  
99          if (previousMockUp != null) {
100             targetType = previousMockUp.targetType;
101             mockedClass = previousMockUp.mockedClass;
102             return;
103         }
104 
105         targetType = getTypeToFake();
106         Class<T> classToMock = null;
107 
108         if (targetType instanceof Class<?>) {
109             // noinspection unchecked
110             classToMock = (Class<T>) targetType;
111         } else if (targetType instanceof ParameterizedType) {
112             ParameterizedType parameterizedType = (ParameterizedType) targetType;
113             // noinspection unchecked
114             classToMock = (Class<T>) parameterizedType.getRawType();
115         }
116 
117         if (classToMock != null) {
118             mockedClass = redefineClassOrImplementInterface(classToMock);
119         } else {
120             Type[] typesToMock = ((TypeVariable<?>) targetType).getBounds();
121 
122             mockedClass = typesToMock.length > 1
123                     ? new FakedImplementationClass<T>(this).createImplementation(typesToMock)
124                     : new CaptureOfFakedImplementations(this, typesToMock[0]).apply();
125         }
126     }
127 
128     @Nullable
129     private MockUp<?> findPreviouslyFakedClassIfMockUpAlreadyApplied() {
130         FakeClasses mockClasses = TestRun.getFakeClasses();
131         FakeClasses.MockUpInstances mockUpInstances = mockClasses.findPreviouslyAppliedMockUps(this);
132 
133         if (mockUpInstances != null && mockUpInstances.hasMockUpsForSingleInstances()) {
134             return mockUpInstances.initialMockUp;
135         }
136 
137         return null;
138     }
139 
140     /**
141      * Gets the type to fake.
142      *
143      * @return the type to fake
144      */
145     @NonNull
146     private Type getTypeToFake() {
147         Class<?> currentClass = getClass();
148 
149         do {
150             Type superclass = currentClass.getGenericSuperclass();
151 
152             if (superclass instanceof ParameterizedType) {
153                 return ((ParameterizedType) superclass).getActualTypeArguments()[0];
154             }
155 
156             if (superclass == MockUp.class) {
157                 throw new IllegalArgumentException("No target type");
158             }
159 
160             currentClass = (Class<?>) superclass;
161         } while (true);
162     }
163 
164     @NonNull
165     private Class<?> redefineClassOrImplementInterface(@NonNull Class<T> classToMock) {
166         if (classToMock.isInterface()) {
167             return createInstanceOfMockedImplementationClass(classToMock, targetType);
168         }
169 
170         Class<T> realClass = classToMock;
171 
172         if (isAbstract(classToMock.getModifiers())) {
173             classToMock = new ConcreteSubclass<T>(classToMock).generateClass();
174         }
175 
176         redefineMethods(realClass, classToMock, targetType);
177         return classToMock;
178     }
179 
180     @NonNull
181     private Class<T> createInstanceOfMockedImplementationClass(@NonNull Class<T> classToMock,
182             @Nullable Type typeToMock) {
183         return new FakedImplementationClass<T>(this).createImplementation(classToMock, typeToMock);
184     }
185 
186     private void redefineMethods(@NonNull Class<T> realClass, @NonNull Class<T> classToMock,
187             @Nullable Type genericMockedType) {
188         new FakeClassSetup(realClass, classToMock, genericMockedType, this).redefineMethods();
189     }
190 
191     /**
192      * Applies the {@linkplain Mock mock methods} defined in the mock-up subclass to the given class/interface.
193      * <p>
194      * In most cases, the constructor with no parameters can be used. This variation should be used only when the type
195      * to be faked is not accessible or known from the test code.
196      *
197      * @param targetClass
198      *            the target class
199      *
200      * @see #MockUp()
201      * @see #MockUp(Object)
202      */
203     protected MockUp(Class<?> targetClass) {
204         targetType = targetClass;
205         MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();
206 
207         if (previousMockUp != null) {
208             mockedClass = previousMockUp.mockedClass;
209             return;
210         }
211 
212         if (targetClass.isInterface()) {
213             // noinspection unchecked
214             mockedClass = createInstanceOfMockedImplementationClass((Class<T>) targetClass, targetClass);
215         } else {
216             mockedClass = targetClass;
217             // noinspection unchecked
218             Class<T> realClass = (Class<T>) targetClass;
219             redefineMethods(realClass, realClass, null);
220             mockInstance = null;
221         }
222     }
223 
224     /**
225      * Applies the {@linkplain Mock mock methods} defined in the mock-up subclass to the type specified through the type
226      * parameter, but only affecting the given instance.
227      * <p>
228      * In most cases, the constructor with no parameters should be adequate. This variation can be used when mock data
229      * or behavior is desired only for a particular instance, with other instances remaining unaffected; or when
230      * multiple mock-up objects carrying different states are desired, with one mock-up instance per real instance.
231      * <p>
232      * If {@link #getMockInstance()} later gets called on this mock-up instance, it will return the instance that was
233      * given here.
234      *
235      * @param targetInstance
236      *            a real instance of the type to be faked, meant to be the only one of that type that should be affected
237      *            by this mock-up instance
238      *
239      * @see #MockUp()
240      * @see #MockUp(Class)
241      */
242     protected MockUp(T targetInstance) {
243         MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();
244 
245         if (previousMockUp != null) {
246             targetType = previousMockUp.targetType;
247             mockedClass = previousMockUp.mockedClass;
248             setMockInstance(targetInstance);
249             return;
250         }
251 
252         @SuppressWarnings("unchecked")
253         Class<T> classToMock = (Class<T>) targetInstance.getClass();
254         targetType = classToMock;
255         mockedClass = classToMock;
256         redefineMethods(classToMock, classToMock, classToMock);
257 
258         setMockInstance(targetInstance);
259     }
260 
261     private void setMockInstance(@NonNull T mockInstance) {
262         TestRun.getFakeClasses().addFake(this, mockInstance);
263         this.mockInstance = mockInstance;
264     }
265 
266     /**
267      * Returns the mock instance exclusively associated with this mock-up instance. If the mocked type was an interface,
268      * then said instance is the one that was automatically created when the mock-up was applied. If it was a class, and
269      * no such instance is currently associated with this (stateful) mock-up object, then a new <em>uninitialized</em>
270      * instance of the faked class is created and returned, becoming associated with the mock-up. If a regular
271      * <em>initialized</em> instance was desired, then the {@link #MockUp(Object)} constructor should have been used
272      * instead.
273      * <p>
274      * In any case, for a given mock-up instance this method will always return the same mock instance.
275      *
276      * @return the mock instance
277      *
278      * @see <a href="http://jmockit.github.io/tutorial/Faking.html#interfaces" target="tutorial">Tutorial</a>
279      */
280     public final T getMockInstance() {
281         if (invokedInstance == Void.class) {
282             return null;
283         }
284 
285         if (invokedInstance != null) {
286             return invokedInstance;
287         }
288 
289         if (mockInstance == null && mockedClass != null) {
290             @SuppressWarnings("unchecked")
291             T newInstance = (T) createMockInstance(mockedClass);
292             setMockInstance(newInstance);
293         }
294 
295         return mockInstance;
296     }
297 
298     @NonNull
299     private Object createMockInstance(@NonNull Class<?> mockedClass) {
300         String mockedClassName = mockedClass.getName();
301 
302         if (isGeneratedImplementationClassName(mockedClassName)) {
303             return ConstructorReflection.newInstanceUsingPublicDefaultConstructor(mockedClass);
304         }
305 
306         if (Proxy.isProxyClass(mockedClass)) {
307             return MockInvocationHandler.newMockedInstance(mockedClass);
308         }
309 
310         return ConstructorReflection.newUninitializedInstance(mockedClass);
311     }
312 
313     /**
314      * An empty method that can be overridden in a mock-up subclass that wants to be notified whenever the mock-up is
315      * automatically torn down. Tear down happens when the mock-up goes out of scope: at the end of the test when
316      * applied inside a test, at the end of the test class when applied before the test class, or at the end of the test
317      * run when applied through the "<code>mockups</code>" system property.
318      * <p>
319      * By default, this method does nothing.
320      */
321     protected void onTearDown() {
322     }
323 }