@weixin
2014-12-09T18:58:03.000000Z
字数 9104
阅读 1644
mock
static final class Subclass extends BaseClass
{
final int INITIAL_VALUE = new Random().nextInt();
final int initialValue = -1;
private static final Integer constantField = 123;
private static final String compileTimeConstantField = "test";
static final boolean FLAG = false;
private static StringBuilder buffer;
private static char static1;
private static char static2;
static StringBuilder getBuffer() { return buffer; }
static void setBuffer(StringBuilder buffer) { Subclass.buffer = buffer; }
private String stringField;
private int intField;
private int intField2;
private List<String> listField;
Subclass() { intField = -1; }
Subclass(int a, String b) { intField = a; stringField = b; }
Subclass(String... args) { listField = Arrays.asList(args); }
Subclass(List<String> list) { listField = list; }
private static Boolean anStaticMethod() { return true; }
private static void staticMethod(short s, String str, Boolean b) {}
private static String staticMethod(short s, StringBuilder str, boolean b) { return String.valueOf(str); }
private long aMethod() { return 567L; }
private void instanceMethod(short s, String str, Boolean b) {}
private String instanceMethod(short s, StringBuilder str, boolean b) { return String.valueOf(str); }
int getIntField() { return intField; }
void setIntField(int intField) { this.intField = intField; }
int getIntField2() { return intField2; }
void setIntField2(int intField2) { this.intField2 = intField2; }
String getStringField() { return stringField; }
void setStringField(String stringField) { this.stringField = stringField; }
List<String> getListField() { return listField; }
void setListField(List<String> listField) { this.listField = listField; }
private final class InnerClass
{
private InnerClass() {}
private InnerClass(boolean b, Long l, String s) {}
private InnerClass(List<String> list) {}
}
}
Here is the BaseClass
public class BaseClass {
protected int baseInt;
protected String baseString;
protected Set<Boolean> baseSet;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private long longField;
public static void doStatic1() { throw new RuntimeException("Real method 1 called"); }
public static void doStatic2() { throw new RuntimeException("Real method 2 called"); }
public void doSomething1() { throw new RuntimeException("Real method 1 called"); }
public void doSomething2() { throw new RuntimeException("Real method 2 called"); }
void setLongField(long value) { longField = value; }
}
test method
public void getInstanceFieldByType()
{
anInstance.setStringField("by type");
anInstance.setListField(new ArrayList<String>());
String stringValue = getField(anInstance, String.class);
List<String> listValue = getField(anInstance, List.class);
List<String> listValue2 = getField(anInstance, ArrayList.class);
assertEquals(anInstance.getStringField(), stringValue);
assertSame(anInstance.getListField(), listValue);
assertSame(listValue, listValue2);
}
Deencapsulation.getField is not able to get field by name, it could also get field by type. that's really powerful.
But if there are multiple fields with the same time, what to do:
@Test
public void attemptToGetInstanceFieldByTypeForClassWithMultipleFieldsOfThatType()
{
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("More than one instance field");
thrown.expectMessage("of type int ");
thrown.expectMessage("INITIAL_VALUE, initialValue");
getField(anInstance, int.class);
}
you would get exception
get inherited instace field
@SuppressWarnings("unchecked")
@Test
public void getInheritedInstanceFieldByType()
{
Set<Boolean> fieldValueOnInstance = new HashSet<Boolean>();
anInstance.baseSet = fieldValueOnInstance;
Set<Boolean> setValue = getField(anInstance, fieldValueOnInstance.getClass());
Set<Boolean> setValue2 = getField(anInstance, HashSet.class);
assertSame(fieldValueOnInstance, setValue);
assertSame(setValue, setValue2);
}
you can both use instance.getClass()
and exact type HashSet.class
@Test
public void getStaticFieldByType()
{
Subclass.setBuffer(new StringBuilder());
StringBuilder b = getField(Subclass.class, StringBuilder.class);
assertSame(Subclass.getBuffer(), b);
}
SubClass.buf is a static field.
@Test
public void setFinalInstanceFields()
{
Subclass obj = new Subclass();
// setField(obj, "INITIAL_VALUE", 123);
setField(obj, "initialValue", 123);
// assertEquals(123, obj.INITIAL_VALUE);
System.out.println("INI : " + getField(obj,"INITIAL_VALUE" ));
assertEquals(123, getField(obj, "initialValue"));
assertEquals(-1, obj.initialValue); // in this case, the compile-time constant gets embedded in client code
}
both INITIAL_VALUE
and initialValue
is final, one is random one is not?
my guessing is :
for INITAL_VALUE, at compile time, the value is not determined yet, because of the Random call. the value got set at run time. once it set, it won't be changed.
I put some diagnostic code
@Test
public void setFinalInstanceFields()
{
Subclass obj = new Subclass();
// setField(obj, "INITIAL_VALUE", 123);
setField(obj, "initialValue", 123);
// setField(obj, "initialValue2", 456);
// assertEquals(123, obj.INITIAL_VALUE);
System.out.println("INI : " + getField(obj,"INITIAL_VALUE" ));
System.out.println("initial2 : " + getField(obj,"initialValue2" ));
assertEquals(obj.initialValue2, getField(obj,"initialValue2" ));
assertEquals(123, getField(obj, "initialValue"));
assertEquals(-1, obj.initialValue); // in this case, the compile-time constant gets embedded in client code
// assertEquals(456, obj.initialValue2); // in this case, the compile-time constant gets embedded in client code
}
output :
NI : 1222261039
initial2 : -810538442
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.67 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
as you can see assertEquals(obj.initialValue2, getField(obj,"initialValue2" ));
, initialValue2 is a random value like INITIAL_VALUE, once it was set at run time, it is a fixed value. it doens't have compiling time value.
@Test
public void invokeInstanceMethodWithSpecifiedParameterTypes()
{
String result =
invoke(
anInstance, "instanceMethod",
new Class<?>[] {short.class, StringBuilder.class, boolean.class},
(short) 7, new StringBuilder("abc"), true);
assertEquals("abc", result);
}
see the usage of invoke
, could specify the paramter types. but you don't have to :
@Test
public void invokeInstanceMethodWithMultipleParameters()
{
assertNull(invoke(anInstance, "instanceMethod", (short) 7, "abc", true));
String result = invoke(anInstance, "instanceMethod", (short) 7, new StringBuilder("abc"), true);
assertEquals("abc", result);
}
How to invoke static method
@Test
public void invokeStaticMethodWithoutParameters()
{
Boolean result = invoke(Subclass.class, "anStaticMethod");
assertTrue(result);
}
create a instance by calling non-args constructor
@Test
public void newInstanceUsingNoArgsConstructorFromSpecifiedParameterTypes()
{
Subclass instance = newInstance(Subclass.class.getName(), new Class<?>[] {});
assertNotNull(instance);
assertEquals(-1, instance.getIntField());
}
or more concisely , do something like this:
@Test
public void newInstanceUsingNoArgsConstructorWithoutSpecifyingParameters()
{
Subclass instance = newInstance(Subclass.class.getName());
assertNotNull(instance);
assertEquals(-1, instance.getIntField());
}
@Test
public void newInnerInstanceUsingNoArgsConstructor()
{
Object innerInstance = newInnerInstance("InnerClass", anInstance);
assertTrue(innerClass.isInstance(innerInstance));
}
notice the usage newInnerInstance
, that's really convinient.
but if you are unlucky, you happened to have a class with the same nanme of inner class,
class InnerClass { InnerClass(int i) {} }
then what you should do
try this , what you would get:
@Test
public void instantiateInnerClassWithOwnerInstance()
{
// InnerClass ic = newInstance(InnerClass.class, this, 123);
InnerClass ic = newInstance(InnerClass.class, 123);
assertNotNull(ic);
}
result:
instantiateInnerClassWithOwnerInstance(com.weixin.test.DeencapsulationTest) Time elapsed: 0.017 sec <<< ERROR!
java.lang.IllegalArgumentException: Invalid instantiation of inner class; use newInnerInstance instead
at com.weixin.test.DeencapsulationTest.instantiateInnerClassWithOwnerInstance(DeencapsulationTest.java:617)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.reflect.NativeMethodAccessorImpl.inv
in this scenario, you have to put this
as the ownner class, as shown in the above commented code InnerClass ic = newInstance(InnerClass.class, this, 123);
create a inner class instance with argumented constructor
@Test
public void newInnerInstanceByNameUsingMultipleArgsConstructor()
{
Object innerInstance = newInnerInstance("InnerClass", anInstance, true, 5L, "");
assertTrue(innerClass.isInstance(innerInstance));
}
you could also do this way:
@Test
public void newInnerInstanceUsingMultipleArgsConstructor()
{
Object innerInstance = newInnerInstance(innerClass, anInstance, true, 5L, "");
assertTrue(innerClass.isInstance(innerInstance));
}
what's the 'innerClass', in the above example, you use the literal Class string name, "InnerClass"
, here is the explaination:
static final Class<?> innerClass = ClassLoad.loadClass(Subclass.class.getName() + "$InnerClass");
that's really cool.
One thing that's really powerful is that you can even mock the private inner class and private method which is not accessible from outside code. By using jmockit, you could test the legacy code easily. see an example :
Object mfaQuestioinStripper = Deencapsulation
.newInnerInstance("CC2QuestionInfoStripper", service, mfaType, q, questionsList);
Assert.assertNotNull(mfaQuestioinStripper);
Deencapsulation.invoke(mfaQuestioinStripper, "stripQuestionsInfo");
In service class, the CC2QuestionInfoStripper
is a protected inner class, so you are not able to reference to it if you are outside of his package. but with jmockit 's help, you could easily new an instance of it, also invoke method it has. that's really convinient. need to study how it is implemented.