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