/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit;
import java.lang.reflect.*;
import java.util.*;
import javax.annotation.*;
import static java.lang.reflect.Modifier.*;
import mockit.internal.classGeneration.*;
import mockit.internal.mockups.*;
import mockit.internal.reflection.*;
import mockit.internal.startup.*;
import mockit.internal.state.MockClasses.*;
import mockit.internal.state.*;
import static mockit.internal.util.GeneratedClasses.*;
/**
* A base class used in the creation of a <em>mock-up</em> for an <em>external</em> type, which is usually a class from
* some library or component used from the <em>internal</em> codebase of the system under test (SUT).
* Such mock-ups can be used as <em>fake</em> implementations for use in unit or integration tests.
* For example:
* <pre>
* public final class FakeSystem <strong>extends MockUp<System></strong> {
* <strong>@Mock</strong> public static long nanoTime() { return 123L; }
* }
* </pre>
* One or more <em>mock methods</em> annotated {@linkplain Mock as such} must be defined in the concrete subclass.
* Each {@code @Mock} method should have a matching method or constructor in the faked class/interface.
* At runtime, the execution of a faked method/constructor will get redirected to the corresponding mock method.
* <p/>
* When the faked type is an interface, an implementation class is generated where all methods are empty, with non-void
* methods returning a default value according to the return type: {@code 0} for {@code int}, {@code null} for a
* reference type, and so on.
* In this case, an instance of the generated implementation class should be obtained by calling
* {@link #getMockInstance()}.
* <p/>
* When the type to be faked is specified indirectly through a {@linkplain TypeVariable type variable}, there are two
* other possible outcomes:
* <ol>
* <li>If the type variable "<code>extends</code>" two or more interfaces, a mock proxy class that implements all
* interfaces is created, with the proxy instance made available through a call to {@link #getMockInstance()}.
* Example:
* <pre>
* @Test
* public <<strong>M extends Runnable & ResultSet</strong>> void someTest() {
* M mock = new MockUp<<strong>M</strong>>() {
* @Mock void run() { ...do something... }
* @Mock boolean next() { return true; }
* }.getMockInstance();
*
* mock.run();
* assertTrue(mock.next());
* }
* </pre>
* </li>
* <li>If the type variable extends a <em>single</em> type (either an interface or a non-<code>final</code> class), then
* that type is taken as a <em>base</em> type whose concrete implementation classes should <em>also</em> get faked.
* Example:
* <pre>
* @Test
* public <<strong>BC extends SomeBaseClass</strong>> void someTest() {
* new MockUp<<strong>BC</strong>>() {
* @Mock int someMethod(int i) { return i + 1; }
* };
*
* int i = new AConcreteSubclass().someMethod(1);
* assertEquals(2, i);
* }
* </pre>
* </li>
* </ol>
*
* @param <T> specifies the type (class, interface, etc.) to be faked; multiple interfaces can be faked by defining a
* <em>type variable</em> in the test class or test method, and using it as the type argument;
* if a type variable is used and it extends a <em>single</em> type, then all implementation classes extending or
* implementing that base type are also faked;
* if the type argument itself is a parameterized type, then only its raw type is considered
*
* @see #MockUp()
* @see #MockUp(Class)
* @see #MockUp(Object)
* @see #getMockInstance()
* @see #onTearDown()
* @see #targetType
* @see <a href="http://jmockit.org/tutorial/Faking.html#setUp">Tutorial</a>
*/
public abstract class MockUp<T>
{
static { Startup.verifyInitialization(); }
/**
* Holds the class or generic type targeted by this mock-up instance.
*/
protected final Type targetType;
@Nullable private final Class<?> mockedClass;
@Nullable private Set<Class<?>> classesToRestore;
@Nullable private T mockInstance;
@Nullable private T invokedInstance;
/**
* Applies the {@linkplain Mock mock methods} defined in the concrete subclass to the class or interface specified
* through the type parameter.
*
* @see #MockUp(Class)
* @see #MockUp(Object)
*/
protected MockUp()
{
MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();
if (previousMockUp != null) {
targetType = previousMockUp.targetType;
mockedClass = previousMockUp.mockedClass;
return;
}
targetType = getTypeToFake();
Class<T> classToMock = null;
if (targetType instanceof Class<?>) {
//noinspection unchecked
classToMock = (Class<T>) targetType;
}
else if (targetType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) targetType;
//noinspection unchecked
classToMock = (Class<T>) parameterizedType.getRawType();
}
if (classToMock != null) {
mockedClass = redefineClassOrImplementInterface(classToMock);
}
else {
Type[] typesToMock = ((TypeVariable<?>) targetType).getBounds();
mockedClass = typesToMock.length > 1 ?
new MockedImplementationClass<T>(this).createImplementation(typesToMock) :
new CaptureOfMockedUpImplementations(this, typesToMock[0]).apply();
}
}
@Nullable
private MockUp<?> findPreviouslyFakedClassIfMockUpAlreadyApplied()
{
MockClasses mockClasses = TestRun.getMockClasses();
MockUpInstances mockUpInstances = mockClasses.findPreviouslyAppliedMockUps(this);
if (mockUpInstances != null && mockUpInstances.hasMockUpsForSingleInstances()) {
return mockUpInstances.initialMockUp;
}
return null;
}
@Nonnull
private Type getTypeToFake()
{
Class<?> currentClass = getClass();
do {
Type superclass = currentClass.getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
return ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
if (superclass == MockUp.class) {
throw new IllegalArgumentException("No target type");
}
currentClass = (Class<?>) superclass;
}
while (true);
}
@Nonnull
private Class<?> redefineClassOrImplementInterface(@Nonnull Class<T> classToMock)
{
if (classToMock.isInterface()) {
return createInstanceOfMockedImplementationClass(classToMock, targetType);
}
Class<T> realClass = classToMock;
if (isAbstract(classToMock.getModifiers())) {
classToMock = new ConcreteSubclass<T>(classToMock).generateClass();
}
classesToRestore = redefineMethods(realClass, classToMock, targetType);
return classToMock;
}
@Nonnull
private Class<T> createInstanceOfMockedImplementationClass(@Nonnull Class<T> classToMock, @Nullable Type typeToMock)
{
return new MockedImplementationClass<T>(this).createImplementation(classToMock, typeToMock);
}
@Nullable
private Set<Class<?>> redefineMethods(
@Nonnull Class<T> realClass, @Nonnull Class<T> classToMock, @Nullable Type genericMockedType)
{
return new MockClassSetup(realClass, classToMock, genericMockedType, this).redefineMethods();
}
/**
* Applies the {@linkplain Mock mock methods} defined in the mock-up subclass to the given class/interface.
* <p/>
* In most cases, the constructor with no parameters can be used.
* This variation should be used only when the type to be faked is not accessible or known from the test code.
*
* @see #MockUp()
* @see #MockUp(Object)
*/
protected MockUp(@SuppressWarnings("NullableProblems") Class<?> targetClass)
{
targetType = targetClass;
MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();
if (previousMockUp != null) {
mockedClass = previousMockUp.mockedClass;
return;
}
if (targetClass.isInterface()) {
//noinspection unchecked
mockedClass = createInstanceOfMockedImplementationClass((Class<T>) targetClass, targetClass);
}
else {
mockedClass = targetClass;
//noinspection unchecked
Class<T> realClass = (Class<T>) targetClass;
classesToRestore = redefineMethods(realClass, realClass, null);
mockInstance = null;
}
}
/**
* Applies the {@linkplain Mock mock methods} defined in the mock-up subclass to the type specified through the type
* parameter, but only affecting the given instance.
* <p/>
* In most cases, the constructor with no parameters should be adequate.
* This variation can be used when mock data or behavior is desired only for a particular instance, with other
* instances remaining unaffected; or when multiple mock-up objects carrying different states are desired, with one
* mock-up instance per real instance.
* <p/>
* If {@link #getMockInstance()} later gets called on this mock-up instance, it will return the instance that was
* given here.
*
* @param targetInstance a real instance of the type to be faked, meant to be the only one of that type that should
* be affected by this mock-up instance
*
* @see #MockUp()
* @see #MockUp(Class)
*/
protected MockUp(T targetInstance)
{
MockUp<?> previousMockUp = findPreviouslyFakedClassIfMockUpAlreadyApplied();
if (previousMockUp != null) {
targetType = previousMockUp.targetType;
mockedClass = previousMockUp.mockedClass;
setMockInstance(targetInstance);
return;
}
@SuppressWarnings("unchecked") Class<T> classToMock = (Class<T>) targetInstance.getClass();
targetType = classToMock;
mockedClass = classToMock;
classesToRestore = redefineMethods(classToMock, classToMock, classToMock);
setMockInstance(targetInstance);
}
private void setMockInstance(@Nonnull T mockInstance)
{
TestRun.getMockClasses().addMock(this, mockInstance);
this.mockInstance = mockInstance;
}
/**
* Returns the mock instance exclusively associated with this mock-up instance.
* If the mocked type was an interface, then said instance is the one that was automatically created when the mock-up
* was applied.
* If it was a class, and no such instance is currently associated with this (stateful) mock-up object, then a new
* <em>uninitialized</em> instance of the faked class is created and returned, becoming associated with the mock-up.
* If a regular <em>initialized</em> instance was desired, then the {@link #MockUp(Object)} constructor should have
* been used instead.
* <p/>
* In any case, for a given mock-up instance this method will always return the same mock instance.
*
* @see <a href="http://jmockit.org/tutorial/Faking.html#interfaces">Tutorial</a>
*/
public final T getMockInstance()
{
if (invokedInstance == Void.class) {
return null;
}
if (invokedInstance != null) {
return invokedInstance;
}
if (mockInstance == null && mockedClass != null) {
@SuppressWarnings("unchecked") T newInstance = (T) createMockInstance(mockedClass);
setMockInstance(newInstance);
}
//noinspection ConstantConditions
return mockInstance;
}
@Nonnull
private Object createMockInstance(@Nonnull Class<?> mockedClass)
{
String mockedClassName = mockedClass.getName();
if (isGeneratedImplementationClass(mockedClassName)) {
return ConstructorReflection.newInstanceUsingPublicDefaultConstructor(mockedClass);
}
if (Proxy.isProxyClass(mockedClass)) {
return MockInvocationHandler.newMockedInstance(mockedClass);
}
return ConstructorReflection.newUninitializedInstance(mockedClass);
}
/**
* An empty method that can be overridden in a mock-up subclass that wants to be notified whenever the mock-up is
* automatically torn down.
* Tear down happens when the mock-up goes out of scope: at the end of the test when applied inside a test, at the
* end of the test class when applied before the test class, or at the end of the test run when applied through the
* "<code>mockups</code>" system property.
* <p/>
* By default, this method does nothing.
*/
protected void onTearDown() {}
}