mockit.MockUp Maven / Gradle / Ivy
/*
* Copyright (c) 2006 JMockit developers
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit;
import java.lang.reflect.*;
import javax.annotation.*;
import mockit.internal.faking.*;
import mockit.internal.startup.*;
/**
* A base class used in the creation of a fake for an external type, which is usually a class from some library or
* component used from the internal codebase of the system under test (SUT).
* Such fake classes can be used as fake implementations for use in unit or integration tests.
* For example:
*
* public final class FakeSystem extends MockUp<System> {
* @Mock public static long nanoTime() { return 123L; }
* }
*
* One or more fake methods annotated {@linkplain Mock as such} must be defined in the concrete subclass.
* Each @Mock method should have a matching method or constructor in the faked class.
* At runtime, the execution of a faked method/constructor will get redirected to the corresponding fake method.
*
* When the type to be faked is specified indirectly through a {@linkplain TypeVariable type variable}, then that type is taken as a
* base type whose concrete implementation classes should also get faked.
* Example:
*
* @Test
* public <BC extends SomeBaseClass> void someTest() {
* new MockUp<BC>() {
* @Mock int someMethod(int i) { return i + 1; }
* };
*
* int i = new AConcreteSubclass().someMethod(1);
* assertEquals(2, i);
* }
*
*
* @param specifies the type to be faked; if a type variable is used, 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 #onTearDown()
* @see #targetType
* @see Tutorial
*/
public class MockUp
{
static { Startup.verifyInitialization(); }
/**
* Holds the class or generic type targeted by this fake instance.
*/
@Nonnull protected final Type targetType;
/**
* Applies the {@linkplain Mock fake methods} defined in the concrete subclass to the class specified through the type parameter.
*/
protected MockUp() {
targetType = getTypeToFake();
Class classToFake = null;
if (targetType instanceof Class) {
//noinspection unchecked
classToFake = (Class) targetType;
}
else if (targetType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) targetType;
//noinspection unchecked
classToFake = (Class) parameterizedType.getRawType();
}
if (classToFake != null) {
redefineClass(classToFake);
}
else {
Type[] typesToFake = ((TypeVariable) targetType).getBounds();
if (typesToFake.length == 1) {
new CaptureOfFakedImplementations(this, typesToFake[0]).apply();
}
else {
throw new UnsupportedOperationException("Unable to capture more than one base type at once");
}
}
}
@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);
}
private void redefineClass(@Nonnull Class classToFake) {
if (!classToFake.isInterface()) {
new FakeClassSetup(classToFake, this, targetType).redefineMethods();
}
}
/**
* Applies the {@linkplain Mock fake methods} defined in the fake class to the given class.
*
* In most cases, the {@linkplain #MockUp() constructor with no parameters} can be used.
* This variation is useful when the type to be faked is not known at compile time.
* For example, it can be used with an {@linkplain Mock $advice} method and the fakes system property in order to have an
* aspect-like fake implementation applicable to any class; it can then be applied at the beginning of the test run with the desired
* target class being specified in the test run configuration.
*/
protected MockUp(@Nonnull Class targetClass) {
targetType = targetClass;
redefineClass(targetClass);
}
/**
* An empty method that can be overridden in a fake class that wants to be notified whenever the fake is automatically torn down.
* Tear down happens when the fake 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 "fakes
" system property.
*
* By default, this method does nothing.
*/
protected void onTearDown() {}
}