mockit.MockUp Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmockit Show documentation
Show all versions of jmockit Show documentation
JMockit is a Java toolkit for automated developer testing.
It contains mocking/faking APIs and a code coverage tool, supporting both JUnit and TestNG.
The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested
in isolation from selected dependencies.
/*
* 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.startup.*;
import mockit.internal.state.MockClasses.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
import static mockit.internal.util.GeneratedClasses.*;
/**
* A base class used in the creation of a mock-up 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 mock-ups 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 mock methods 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.
*
* 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()}.
*
* When the type to be faked is specified indirectly through a {@linkplain TypeVariable type variable}, there are two
* other possible outcomes:
*
* - If the type variable "
extends
" 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:
*
* @Test
* public <M extends Runnable & ResultSet> void someTest() {
* M mock = new MockUp<M>() {
* @Mock void run() { ...do something... }
* @Mock boolean next() { return true; }
* }.getMockInstance();
*
* mock.run();
* assertTrue(mock.next());
* }
*
*
* - If the type variable extends a single type (either an interface or a non-
final
class), 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 (class, interface, etc.) to be faked; multiple interfaces can be faked by defining a
* type variable in the test class or test method, and using it as the type argument;
* if a type variable is used and it extends a single 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 Tutorial
*/
public abstract class MockUp
{
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> 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 classToMock = null;
if (targetType instanceof Class>) {
//noinspection unchecked
classToMock = (Class) targetType;
}
else if (targetType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) targetType;
//noinspection unchecked
classToMock = (Class) parameterizedType.getRawType();
}
if (classToMock != null) {
mockedClass = redefineClassOrImplementInterface(classToMock);
}
else {
Type[] typesToMock = ((TypeVariable>) targetType).getBounds();
mockedClass = typesToMock.length > 1 ?
new MockedImplementationClass(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 classToMock)
{
if (classToMock.isInterface()) {
return createInstanceOfMockedImplementationClass(classToMock, targetType);
}
Class realClass = classToMock;
if (isAbstract(classToMock.getModifiers())) {
classToMock = new ConcreteSubclass(classToMock).generateClass();
}
classesToRestore = redefineMethods(realClass, classToMock, targetType);
return classToMock;
}
@Nonnull
private Class createInstanceOfMockedImplementationClass(@Nonnull Class classToMock, @Nullable Type typeToMock)
{
return new MockedImplementationClass(this).createImplementation(classToMock, typeToMock);
}
@Nullable
private Set> redefineMethods(
@Nonnull Class realClass, @Nonnull Class 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.
*
* 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) targetClass, targetClass);
}
else {
mockedClass = targetClass;
//noinspection unchecked
Class realClass = (Class) 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.
*
* 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.
*
* 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 classToMock = (Class) 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
* uninitialized instance of the faked class is created and returned, becoming associated with the mock-up.
* If a regular initialized instance was desired, then the {@link #MockUp(Object)} constructor should have
* been used instead.
*
* In any case, for a given mock-up instance this method will always return the same mock instance.
*
* @see Tutorial
*/
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 newInstance(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
* "mockups
" system property.
*
* By default, this method does nothing.
*/
protected void onTearDown() {}
}