mockit.internal.mockups.MockClassSetup 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 developer (unit/integration) testing.
It contains mocking APIs and other tools, 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-2013 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.mockups;
import java.lang.reflect.*;
import java.lang.reflect.Type;
import java.util.*;
import org.jetbrains.annotations.*;
import mockit.*;
import mockit.external.asm4.*;
import mockit.internal.*;
import mockit.internal.startup.*;
import mockit.internal.state.*;
import mockit.internal.util.*;
public final class MockClassSetup
{
@NotNull private final Class> realClass;
@Nullable private ClassReader rcReader;
@NotNull private final MockMethods mockMethods;
@NotNull private final MockUp> mock;
private final boolean forStartupMock;
public MockClassSetup(@NotNull Class extends MockUp>> mockClass)
{
mock = ConstructorReflection.newInstance(mockClass);
Type typeToMock = getTypeToMock(mockClass);
ParameterizedType mockedType;
if (typeToMock instanceof Class>) {
mockedType = null;
realClass = (Class>) typeToMock;
}
else if (typeToMock instanceof ParameterizedType){
mockedType = (ParameterizedType) typeToMock;
realClass = (Class>) mockedType.getRawType();
}
else {
throw new IllegalArgumentException(
"Invalid target type specified in mock-up " + mockClass + ": " + typeToMock);
}
mockMethods = new MockMethods(realClass, mockedType);
forStartupMock = true;
new MockMethodCollector(mockMethods).collectMockMethods(mockClass);
}
@NotNull public static Type getTypeToMock(@NotNull Class> mockUpClass)
{
Class> currentClass = mockUpClass;
do {
Type superclass = currentClass.getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
return ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
else if (superclass == MockUp.class) {
throw new IllegalArgumentException("No type to be mocked");
}
currentClass = (Class>) superclass;
}
while (true);
}
public MockClassSetup(
@NotNull Class> realClass, @Nullable ParameterizedType mockedType, @NotNull MockUp> mockUp,
@Nullable byte[] realClassCode)
{
this.realClass = realClass;
mockMethods = new MockMethods(realClass, mockedType);
mock = mockUp;
forStartupMock = false;
rcReader = realClassCode == null ? null : new ClassReader(realClassCode);
new MockMethodCollector(mockMethods).collectMockMethods(mockUp.getClass());
}
@NotNull public Set> redefineMethods()
{
Set> redefinedClasses = redefineMethodsInClassHierarchy();
validateThatAllMockMethodsWereApplied();
return redefinedClasses;
}
@NotNull private Set> redefineMethodsInClassHierarchy()
{
Set> redefinedClasses = new HashSet>();
Class> classToModify = realClass;
while (classToModify != null && mockMethods.hasUnusedMocks()) {
byte[] modifiedClassFile = modifyRealClass(classToModify);
if (modifiedClassFile != null) {
applyClassModifications(classToModify, modifiedClassFile);
redefinedClasses.add(classToModify);
}
Class> superClass = classToModify.getSuperclass();
classToModify = superClass == Object.class || superClass == Proxy.class ? null : superClass;
rcReader = null;
}
return redefinedClasses;
}
@Nullable private byte[] modifyRealClass(@NotNull Class> classToModify)
{
if (rcReader == null) {
rcReader = createClassReaderForRealClass(classToModify);
}
MockupsModifier modifier = new MockupsModifier(rcReader, classToModify, mock, mockMethods, forStartupMock);
// Work-around for classes containing JSR/RET instructions, not supported when computing frames.
try {
rcReader.accept(modifier, ClassReader.SKIP_FRAMES);
}
catch (RuntimeException e) {
modifier = new MockupsModifier(rcReader, classToModify, mock, mockMethods, forStartupMock, false);
rcReader.accept(modifier, 0);
}
return modifier.wasModified() ? modifier.toByteArray() : null;
}
@NotNull private ClassReader createClassReaderForRealClass(@NotNull Class> classToModify)
{
if (classToModify.isInterface() || classToModify.isArray()) {
throw new IllegalArgumentException("Not a modifiable class: " + classToModify.getName());
}
return ClassFile.createReaderFromLastRedefinitionIfAny(classToModify);
}
private void applyClassModifications(@NotNull Class> classToModify, @NotNull byte[] modifiedClassFile)
{
Startup.redefineMethods(classToModify, modifiedClassFile);
mockMethods.registerMockStates(forStartupMock);
if (forStartupMock) {
CachedClassfiles.addClassfile(classToModify, modifiedClassFile);
}
else {
String mockClassDesc = mockMethods.getMockClassInternalName();
TestRun.mockFixture().addRedefinedClass(mockClassDesc, classToModify, modifiedClassFile);
}
}
private void validateThatAllMockMethodsWereApplied()
{
List remainingMocks = mockMethods.getUnusedMockSignatures();
if (!remainingMocks.isEmpty()) {
String classDesc = mockMethods.getMockClassInternalName();
String mockSignatures = new MethodFormatter(classDesc).friendlyMethodSignatures(remainingMocks);
throw new IllegalArgumentException(
"Matching real methods not found for the following mocks:\n" + mockSignatures);
}
}
}