All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.MockUp Maven / Gradle / Ivy

/*
 * 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.faking.*;
import mockit.internal.reflection.*;
import mockit.internal.startup.*;
import mockit.internal.faking.FakeClasses.*;
import mockit.internal.state.*;
import static mockit.internal.util.GeneratedClasses.*;

/**
 * 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 {@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 fake 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: *

    *
  1. If the type variable "extends" two or more interfaces, a fake 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 fakedInterfaceInstance = new MockUp<M>() {
     *        @Mock void run() { ...do something... }
     *        @Mock boolean next() { return true; }
     *     }.getMockInstance();
     *
     *     fakedInterfaceInstance.run();
     *     assertTrue(fakedInterfaceInstance.next());
     * }
     * 
    *
  2. *
  3. 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);
     * }
     * 
    *
  4. *
* * @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 fake instance. */ protected final Type targetType; @Nullable private final Class fakedClass; @Nullable private Set> classesToRestore; @Nullable private T fakeInstance; @SuppressWarnings("unused") @Nullable private T invokedInstance; // set through Reflection elsewhere /** * Applies the {@linkplain Mock fake 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 previousFake = findPreviouslyFakedClassIfFakeAlreadyApplied(); if (previousFake != null) { targetType = previousFake.targetType; fakedClass = previousFake.fakedClass; return; } 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) { fakedClass = redefineClassOrImplementInterface(classToFake); } else { Type[] typesToFake = ((TypeVariable) targetType).getBounds(); fakedClass = typesToFake.length > 1 ? new FakedImplementationClass(this).createImplementation(typesToFake) : new CaptureOfFakedImplementations(this, typesToFake[0]).apply(); } } @Nullable private MockUp findPreviouslyFakedClassIfFakeAlreadyApplied() { FakeClasses fakeClasses = TestRun.getFakeClasses(); MockUpInstances fakeInstances = fakeClasses.findPreviouslyAppliedFakes(this); if (fakeInstances != null && fakeInstances.hasMockUpsForSingleInstances()) { return fakeInstances.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 classToFake) { if (classToFake.isInterface()) { return createInstanceOfFakedImplementationClass(classToFake, targetType); } Class realClass = classToFake; if (isAbstract(classToFake.getModifiers())) { classToFake = new ConcreteSubclass(classToFake).generateClass(); } classesToRestore = redefineMethods(realClass, classToFake, targetType); return classToFake; } @Nonnull private Class createInstanceOfFakedImplementationClass(@Nonnull Class classToFake, @Nullable Type typeToFake) { FakedImplementationClass fakedImplementationClass = new FakedImplementationClass(this); return fakedImplementationClass.createImplementation(classToFake, typeToFake); } @Nonnull private Set> redefineMethods( @Nonnull Class realClass, @Nonnull Class classToFake, @Nullable Type genericFakedType) { FakeClassSetup fakeSetup = new FakeClassSetup(realClass, classToFake, genericFakedType, this); return fakeSetup.redefineMethods(); } /** * Applies the {@linkplain Mock fake methods} defined in the fake class 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 previousFake = findPreviouslyFakedClassIfFakeAlreadyApplied(); if (previousFake != null) { fakedClass = previousFake.fakedClass; return; } if (targetClass.isInterface()) { //noinspection unchecked fakedClass = createInstanceOfFakedImplementationClass((Class) targetClass, targetClass); } else { fakedClass = targetClass; //noinspection unchecked Class realClass = (Class) targetClass; classesToRestore = redefineMethods(realClass, realClass, null); fakeInstance = null; } } /** * Applies the {@linkplain Mock fake methods} defined in the fake class 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 fake data or behavior is desired only for a particular instance, with other * instances remaining unaffected; or when multiple fake objects carrying different states are desired, with one * fake instance per real instance. *

* If {@link #getMockInstance()} later gets called on this fake 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 fake instance * * @see #MockUp() * @see #MockUp(Class) * @deprecated This is a rarely used feature without demonstrable use cases; real instances should be used instead. */ @Deprecated protected MockUp(T targetInstance) { MockUp previousFake = findPreviouslyFakedClassIfFakeAlreadyApplied(); if (previousFake != null) { targetType = previousFake.targetType; fakedClass = previousFake.fakedClass; setFakeInstance(targetInstance); return; } @SuppressWarnings("unchecked") Class classToFake = (Class) targetInstance.getClass(); targetType = classToFake; fakedClass = classToFake; classesToRestore = redefineMethods(classToFake, classToFake, classToFake); setFakeInstance(targetInstance); } private void setFakeInstance(@Nonnull T fakeInstance) { TestRun.getFakeClasses().addFake(this, fakeInstance); this.fakeInstance = fakeInstance; } /** * Returns the mock instance exclusively associated with this fake instance. * If the faked type was an interface, then said instance is the one that was automatically created when the fake was * applied. * If it was a class, and no such instance is currently associated with this (stateful) fake object, then a new * uninitialized instance of the faked class is created and returned, becoming associated with the fake. * If a regular initialized instance was desired, then the {@link #MockUp(Object)} constructor should have * been used instead. *

* In any case, for a given fake instance this method will always return the same fake instance. * * @see Tutorial */ public final T getMockInstance() { if (invokedInstance == Void.class) { return null; } if (invokedInstance != null) { return invokedInstance; } if (fakeInstance == null && fakedClass != null) { @SuppressWarnings("unchecked") T newInstance = (T) createFakeInstance(fakedClass); setFakeInstance(newInstance); } //noinspection ConstantConditions return fakeInstance; } @Nonnull private Object createFakeInstance(@Nonnull Class fakedClass) { String fakedClassName = fakedClass.getName(); if (isGeneratedImplementationClass(fakedClassName)) { return ConstructorReflection.newInstanceUsingPublicDefaultConstructor(fakedClass); } if (Proxy.isProxyClass(fakedClass)) { return MockInvocationHandler.newMockedInstance(fakedClass); } return ConstructorReflection.newUninitializedInstance(fakedClass); } /** * 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() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy