The utility methods described in this chapter are all static
and stateless.
They are defined in the mockit.Deencapsulation
class.
In the end, these methods simply wrap some of the standard functionality available in the Java
Reflection API in
a more convenient, higher-level API.
As such, they can be used in all kinds of tests, not just those which use the JMockit mocking APIs.
The following test class will be used for example tests in the sub-sections that follow:
import static mockit.Deencapsulation.*;
public final class DeencapsulationTest
{
final SubClass anInstance = new SubClass();
static class BaseClass
{
protected int baseInt;
protected String baseString;
protected Set<Boolean> baseSet;
}
static final class SubClass extends BaseClass
{
private static StringBuilder buffer;
private static char static1;
private static char static2;
private int intField;
private int intField2;
private String stringField;
private List<String> listField;
private SubClass() { intField = -1; }
private SubClass(int a, String b) { intField = a; stringField = b; }
private SubClass(String... args) { listField = Arrays.asList(args); }
private long aMethod() { return 567L; }
private static Boolean anStaticMethod() { return true; }
private void instanceMethod(short s, String str, Boolean b) {}
private static void staticMethod(short s, String str, Boolean b) {}
private final class InnerClass
{
InnerClass() {}
InnerClass(boolean b, Long l, String s) {}
}
}
// example tests
}
The getField
and setField
methods, for which there are several
overloads (variations with different parameters), can be used to access instance and static fields.
The field to be read or assigned can be specified in two ways: by name or by type.
In the case of instance fields, the field may be declared in any class along the hierarchy, not
just in the concrete class of the given owner instance.
The following example tests should clarify the semantics of these methods and their variations.
@Test
public void getSetFieldByName()
{
// Get instance fields:
Integer intValue = getField(anInstance, "intField");
List<String> listValue = getField(anInstance, "listField");
// Get static fields:
StringBuilder b = getField(SubClass.class, "buffer");
// Set instance fields:
setField(anInstance, "intField2", 901);
// Set static fields:
setField(SubClass.class, "buffer", new StringBuilder());
}
@Test
public void getSetFieldByType()
{
// Get instance fields:
String stringValue = getField(anInstance, String.class);
List<String> listValue = getField(anInstance, List.class);
Set<Boolean> setValue = getField(anInstance, HashSet.class);
getField(anInstance, int.class); // ambiguous: will throw an IllegalArgumentException
// Get static fields:
StringBuilder b = getField(SubClass.class, StringBuilder.class);
// Set instance fields:
setField(anInstance, "Test"); // will set SubClass#stringField, not BaseClass#baseString
setField(anInstance, 901); // ambiguous: will throw an IllegalArgumentException
// Set static fields:
setField(SubClass.class, new StringBuilder());
setField(SubClass.class, 'A'); // ambiguous: will throw an IllegalArgumentException
}
Access through fields by name is safe, as there is no possibility for ambiguity in the case of instance fields declared in multiple classes related by inheritance. The same doesn't apply for access by type, since different classes in the hierarchy may declare fields of the same type but with different names. If an attempt is made to access such a field by its type, an exception will be thrown.
There are only two utility methods for invoking non-accessible methods: one for instance methods
and another for static
methods.
In both cases, the name of the method to be invoked is specified in a string.
The invocation arguments, one for each declared parameter of the target method, are passed
through a "varargs" parameter of the utility method:
@Test
public void invokeMethod()
{
// Instance methods:
Long l = invoke(anInstance, "aMethod");
String s = invoke(anInstance, "instanceMethod", (short) 7, "abc", true);
// Static methods:
Boolean b = invoke(SubClass.class, "anStaticMethod");
Object result = invoke(SubClass.class, "staticMethod", (short) 7, "abc", true);
}
The parameter types for a method are not explicitly specified.
Instead, they are inferred from the given argument values.
A null
argument value, therefore, cannot be passed directly as it not allow the
correct parameter type to be inferred.
It can be specified indirectly, though, by passing the Class
object for the
correct parameter type instead.
If the target class (or a super-class in the case of an instance method) has multiple overloads
for the same method name, then the best match will be chosen based on the parameter types
inferred from the given argument values.
Non-public classes, or classes with no accessible constructors, can be instantiated through
newInstance
and newInnerInstance
methods.
The constructor arguments are passed in the same way as we saw above for method invocations, with the same parameter
inference rules.
@Test
public void newInstance()
{
// Instances of top-level or non-inner nested classes:
SubClass instance = newInstance(SubClass.class.getName());
SubClass instance2 = newInstance("DeencapsulationTest$Subclass", 1, "XYZ");
// Instances of inner classes:
SubClass.InnerClass innerInstance = newInnerInstance("InnerClass", anInstance);
SubClass.InnerClass innerInstance2 = newInnerInstance("InnerClass", anInstance, true, 5L, "");
}
Note that to create an "inner instance" only the simple name of the inner class must be specified. The "outer" class is specified through the required owner instance (the second parameter).