[关闭]
@weixin 2014-12-09T18:58:03.000000Z 字数 9104 阅读 1644

Jmockit study notes 5

mock


Denencapsulation mock test

get instance field by type

  1. static final class Subclass extends BaseClass
  2. {
  3. final int INITIAL_VALUE = new Random().nextInt();
  4. final int initialValue = -1;
  5. private static final Integer constantField = 123;
  6. private static final String compileTimeConstantField = "test";
  7. static final boolean FLAG = false;
  8. private static StringBuilder buffer;
  9. private static char static1;
  10. private static char static2;
  11. static StringBuilder getBuffer() { return buffer; }
  12. static void setBuffer(StringBuilder buffer) { Subclass.buffer = buffer; }
  13. private String stringField;
  14. private int intField;
  15. private int intField2;
  16. private List<String> listField;
  17. Subclass() { intField = -1; }
  18. Subclass(int a, String b) { intField = a; stringField = b; }
  19. Subclass(String... args) { listField = Arrays.asList(args); }
  20. Subclass(List<String> list) { listField = list; }
  21. private static Boolean anStaticMethod() { return true; }
  22. private static void staticMethod(short s, String str, Boolean b) {}
  23. private static String staticMethod(short s, StringBuilder str, boolean b) { return String.valueOf(str); }
  24. private long aMethod() { return 567L; }
  25. private void instanceMethod(short s, String str, Boolean b) {}
  26. private String instanceMethod(short s, StringBuilder str, boolean b) { return String.valueOf(str); }
  27. int getIntField() { return intField; }
  28. void setIntField(int intField) { this.intField = intField; }
  29. int getIntField2() { return intField2; }
  30. void setIntField2(int intField2) { this.intField2 = intField2; }
  31. String getStringField() { return stringField; }
  32. void setStringField(String stringField) { this.stringField = stringField; }
  33. List<String> getListField() { return listField; }
  34. void setListField(List<String> listField) { this.listField = listField; }
  35. private final class InnerClass
  36. {
  37. private InnerClass() {}
  38. private InnerClass(boolean b, Long l, String s) {}
  39. private InnerClass(List<String> list) {}
  40. }
  41. }

Here is the BaseClass

  1. public class BaseClass {
  2. protected int baseInt;
  3. protected String baseString;
  4. protected Set<Boolean> baseSet;
  5. @SuppressWarnings({"FieldCanBeLocal", "unused"})
  6. private long longField;
  7. public static void doStatic1() { throw new RuntimeException("Real method 1 called"); }
  8. public static void doStatic2() { throw new RuntimeException("Real method 2 called"); }
  9. public void doSomething1() { throw new RuntimeException("Real method 1 called"); }
  10. public void doSomething2() { throw new RuntimeException("Real method 2 called"); }
  11. void setLongField(long value) { longField = value; }
  12. }

test method

  1. public void getInstanceFieldByType()
  2. {
  3. anInstance.setStringField("by type");
  4. anInstance.setListField(new ArrayList<String>());
  5. String stringValue = getField(anInstance, String.class);
  6. List<String> listValue = getField(anInstance, List.class);
  7. List<String> listValue2 = getField(anInstance, ArrayList.class);
  8. assertEquals(anInstance.getStringField(), stringValue);
  9. assertSame(anInstance.getListField(), listValue);
  10. assertSame(listValue, listValue2);
  11. }

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:

  1. @Test
  2. public void attemptToGetInstanceFieldByTypeForClassWithMultipleFieldsOfThatType()
  3. {
  4. thrown.expect(IllegalArgumentException.class);
  5. thrown.expectMessage("More than one instance field");
  6. thrown.expectMessage("of type int ");
  7. thrown.expectMessage("INITIAL_VALUE, initialValue");
  8. getField(anInstance, int.class);
  9. }

you would get exception

get inherited instace field

  1. @SuppressWarnings("unchecked")
  2. @Test
  3. public void getInheritedInstanceFieldByType()
  4. {
  5. Set<Boolean> fieldValueOnInstance = new HashSet<Boolean>();
  6. anInstance.baseSet = fieldValueOnInstance;
  7. Set<Boolean> setValue = getField(anInstance, fieldValueOnInstance.getClass());
  8. Set<Boolean> setValue2 = getField(anInstance, HashSet.class);
  9. assertSame(fieldValueOnInstance, setValue);
  10. assertSame(setValue, setValue2);
  11. }

you can both use instance.getClass() and exact type HashSet.class

get static filed

  1. @Test
  2. public void getStaticFieldByType()
  3. {
  4. Subclass.setBuffer(new StringBuilder());
  5. StringBuilder b = getField(Subclass.class, StringBuilder.class);
  6. assertSame(Subclass.getBuffer(), b);
  7. }

SubClass.buf is a static field.

set final intance field

  1. @Test
  2. public void setFinalInstanceFields()
  3. {
  4. Subclass obj = new Subclass();
  5. // setField(obj, "INITIAL_VALUE", 123);
  6. setField(obj, "initialValue", 123);
  7. // assertEquals(123, obj.INITIAL_VALUE);
  8. System.out.println("INI : " + getField(obj,"INITIAL_VALUE" ));
  9. assertEquals(123, getField(obj, "initialValue"));
  10. assertEquals(-1, obj.initialValue); // in this case, the compile-time constant gets embedded in client code
  11. }

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

  1. @Test
  2. public void setFinalInstanceFields()
  3. {
  4. Subclass obj = new Subclass();
  5. // setField(obj, "INITIAL_VALUE", 123);
  6. setField(obj, "initialValue", 123);
  7. // setField(obj, "initialValue2", 456);
  8. // assertEquals(123, obj.INITIAL_VALUE);
  9. System.out.println("INI : " + getField(obj,"INITIAL_VALUE" ));
  10. System.out.println("initial2 : " + getField(obj,"initialValue2" ));
  11. assertEquals(obj.initialValue2, getField(obj,"initialValue2" ));
  12. assertEquals(123, getField(obj, "initialValue"));
  13. assertEquals(-1, obj.initialValue); // in this case, the compile-time constant gets embedded in client code
  14. // assertEquals(456, obj.initialValue2); // in this case, the compile-time constant gets embedded in client code
  15. }

output :

  1. NI : 1222261039
  2. initial2 : -810538442
  3. Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.67 sec
  4. Results :
  5. 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.

How to invoke instance method

  1. @Test
  2. public void invokeInstanceMethodWithSpecifiedParameterTypes()
  3. {
  4. String result =
  5. invoke(
  6. anInstance, "instanceMethod",
  7. new Class<?>[] {short.class, StringBuilder.class, boolean.class},
  8. (short) 7, new StringBuilder("abc"), true);
  9. assertEquals("abc", result);
  10. }

see the usage of invoke, could specify the paramter types. but you don't have to :

  1. @Test
  2. public void invokeInstanceMethodWithMultipleParameters()
  3. {
  4. assertNull(invoke(anInstance, "instanceMethod", (short) 7, "abc", true));
  5. String result = invoke(anInstance, "instanceMethod", (short) 7, new StringBuilder("abc"), true);
  6. assertEquals("abc", result);
  7. }

How to invoke static method

  1. @Test
  2. public void invokeStaticMethodWithoutParameters()
  3. {
  4. Boolean result = invoke(Subclass.class, "anStaticMethod");
  5. assertTrue(result);
  6. }

How to new instance

create a instance by calling non-args constructor

  1. @Test
  2. public void newInstanceUsingNoArgsConstructorFromSpecifiedParameterTypes()
  3. {
  4. Subclass instance = newInstance(Subclass.class.getName(), new Class<?>[] {});
  5. assertNotNull(instance);
  6. assertEquals(-1, instance.getIntField());
  7. }

or more concisely , do something like this:

  1. @Test
  2. public void newInstanceUsingNoArgsConstructorWithoutSpecifyingParameters()
  3. {
  4. Subclass instance = newInstance(Subclass.class.getName());
  5. assertNotNull(instance);
  6. assertEquals(-1, instance.getIntField());
  7. }

How to new instance of inner class

  1. @Test
  2. public void newInnerInstanceUsingNoArgsConstructor()
  3. {
  4. Object innerInstance = newInnerInstance("InnerClass", anInstance);
  5. assertTrue(innerClass.isInstance(innerInstance));
  6. }

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,

  1. class InnerClass { InnerClass(int i) {} }

then what you should do
try this , what you would get:

  1. @Test
  2. public void instantiateInnerClassWithOwnerInstance()
  3. {
  4. // InnerClass ic = newInstance(InnerClass.class, this, 123);
  5. InnerClass ic = newInstance(InnerClass.class, 123);
  6. assertNotNull(ic);
  7. }

result:

  1. instantiateInnerClassWithOwnerInstance(com.weixin.test.DeencapsulationTest) Time elapsed: 0.017 sec <<< ERROR!
  2. java.lang.IllegalArgumentException: Invalid instantiation of inner class; use newInnerInstance instead
  3. at com.weixin.test.DeencapsulationTest.instantiateInnerClassWithOwnerInstance(DeencapsulationTest.java:617)
  4. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  5. at java.lang.reflect.Method.invoke(Method.java:606)
  6. 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

  1. @Test
  2. public void newInnerInstanceByNameUsingMultipleArgsConstructor()
  3. {
  4. Object innerInstance = newInnerInstance("InnerClass", anInstance, true, 5L, "");
  5. assertTrue(innerClass.isInstance(innerInstance));
  6. }

you could also do this way:

  1. @Test
  2. public void newInnerInstanceUsingMultipleArgsConstructor()
  3. {
  4. Object innerInstance = newInnerInstance(innerClass, anInstance, true, 5L, "");
  5. assertTrue(innerClass.isInstance(innerInstance));
  6. }

what's the 'innerClass', in the above example, you use the literal Class string name, "InnerClass", here is the explaination:

  1. static final Class<?> innerClass = ClassLoad.loadClass(Subclass.class.getName() + "$InnerClass");

that's really cool.

An application of mock inaccessible inner class

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 :

  1. Object mfaQuestioinStripper = Deencapsulation
  2. .newInnerInstance("CC2QuestionInfoStripper", service, mfaType, q, questionsList);
  3. Assert.assertNotNull(mfaQuestioinStripper);
  4. 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.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注