在 Java 測試中使用 Mockito 有段時日了,之前只是想固然的認爲 Mock 的對象屬性值和方法返回值都是依據一樣的規則。基本類型是 0, 0.0, 或 false, 對象類型都是 null, Mock 對象的默認返回值也應該是同樣的。直到最近有一天,有一個返回 Optional<String>
類型的方法,因爲忘記對該方法打樁,意外的發現它返回的不是 null, 而 Optional.empty()
, 所以才意識到此處定有蹊蹺。着實有必要用代碼驗證一下 Mockito 是怎麼決定屬性及方法的各類返回類型的默認值的。html
這次測試所用的 Mockito 版本是 mockito-core-2.12.0.java
因而建立了下面一個類 MyClass 用於生成 Mock 對象,選取了一些典型的數據類型, 包括 int, Double, String, long[], Optional<String>, Collection<String>, Map<String, String>, 同時測試 Mock 對象默認的屬性值與方法默認返回值。數組
該類的完整代碼以下:app
1 package cc.unmi; 2 3 import java.util.Collection; 4 import java.util.Map; 5 import java.util.Optional; 6 7 public class MyClass { 8 public int integer; 9 public Double aDouble; 10 public String string; 11 public long[] array; 12 public Optional<String> optional; 13 public Collection<String> collection; 14 public Map<String, String> map; 15 16 public int getInteger() { 17 return 99; 18 } 19 20 public long[] getArray() { 21 return new long[]{0}; 22 } 23 24 public Double getDouble() { 25 return 9.9; 26 } 27 28 public String getString() { 29 return "hello"; 30 } 31 32 public Optional<String> getOptional() { 33 return null; 34 } 35 36 public Collection<String> getCollection() { 37 return null; 38 } 39 40 public Map<String, String> getMap() { 41 return null; 42 } 43 } 44 爲了認識到調用 Mock 對象時默認狀況下不會調用實際方法實現,咱們故意讓上面的方法返回一些亂七八糟的值。 45 46 測試類 MyClassTest 的代碼以下 47 48 1 49 2 50 3 51 4 52 5 53 6 54 7 55 8 56 9 57 10 58 11 59 12 60 13 61 14 62 15 63 16 64 17 65 18 66 19 67 20 68 21 69 22 70 23 71 24 72 25 73 26 74 27 75 28 76 29 77 30 78 31 79 32 80 33 81 34 82 35 83 36 84 package cc.unmi; 85 86 import org.junit.Test; 87 import org.junit.runner.RunWith; 88 import org.mockito.Mockito; 89 import org.mockito.junit.MockitoJUnitRunner; 90 91 @RunWith(MockitoJUnitRunner.class) 92 public class MyClassTest { 93 94 @Test 95 public void watchMockedClass() { 96 MyClass myClass = Mockito.mock(MyClass.class); 97 printDefaults(myClass); 98 } 99 100 private void printDefaults(MyClass myClass) { 101 System.out.println("fields ---- "); 102 System.out.println("integer: " + myClass.integer); 103 System.out.println("array: " + myClass.array); 104 System.out.println("double: " + myClass.aDouble); 105 System.out.println("string: " + myClass.string); 106 System.out.println("optional: " + myClass.optional); 107 System.out.println("collection: " + myClass.collection); 108 System.out.println("map: " + myClass.map); 109 110 System.out.println("\nmethods ---- "); 111 System.out.println("integer: " + myClass.getInteger()); 112 System.out.println("array: " + myClass.getArray()); 113 System.out.println("double: " + myClass.getDouble()); 114 System.out.println("string: " + myClass.getString()); 115 System.out.println("optional: " + myClass.getOptional()); 116 System.out.println("collection: " + myClass.getCollection() + ", " + myClass.getCollection().getClass()); 117 System.out.println("map: " + myClass.getMap() + ", " + myClass.getMap().getClass()); 118 } 119 }
執行上面的代碼輸出以下:測試
fields ---- integer: 0 array: null double: null string: null optional: null collection: null map: null methods ---- integer: 0 array: null double: 0.0 string: null optional: Optional.empty collection: [], class java.util.LinkedList map: {}, class java.util.HashMap
Mockito mock 的對象屬性的默認值沒什麼異議,與 Java 初始化對象的規則一致,基本類型的默認值是 0, 0.0, 或 false。可是對於方法默認返回值就不同了,從上面咱們看到this
- int 類型方法默認返回 0
- long[] 類型方法默認返回 null
- Double 類型方法默認返回 0.0
- string 類型方法默認返回 null
- Optional<String> 類型方法默認返回 Optional.empty
- Collection<String> 類型方法默認返回 new LinkedList<String>(0)
- Map<String, String> 類型方法默認返回 new HashMap<String, String>(0)
關於 Mock 對象屬性的默認值能夠擱一邊,那麼 Mockito 是如何定義 Mock 對象方法的默認返回值的呢?url
一般的,咱們建立一個 Mock 對象都是簡單的調用 Mockito 的以下方法:spa
1 public static <T> T mock(Class<T> classToMock) { 2 return mock(classToMock, withSetting()); 3 }
再看 withSetting()
方法:.net
1 public static MockSetting withSetting() { 2 return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS); 3 }
繞了一圈,基實咱們默認採用的 Mock 對象的方式其實就是以下:rest
Mockito.mock(MyClass.class, Answers.RETURNS_DEFAULTS);
在 org.mockito.Answers
中定義了以下設定方法默認返回值的選項
- RETURN_DEFAULTS(new GloballyConfiguredAnswer()) -- 基本對應到 ReturnsEmptyValues 實現
- RETURNS_SMART_NULLS(new ReturnsSmartNulls()) -- 最後對應到 ReturnsMoreEmptyValues 實現
- RETURN_MOCKS(new ReturnsMocks())
- RETURNS_DEEP_STUBS(new ReturnsDeepStubs())
- CALL_REAL_METHODS(new CallsRealMethods())
- RETURNS_SELF(new TriesToReturnSelf())
因此默認狀況下的 RETURNS_DEFAULTS, Mock 對象方法返回值就是由 ReturnsEmptyValues 類決定的,看這個類的註釋:
Default answer of every Mockito mock. Returns appropriate primitive for primitive-returning methods Returns consistent values for primitive wrapper classes (e.g. int-returning method retuns 0 and Integer-returning method returns 0, too) Returns empty collection for collection-returning methods (works for most commonly used collection types) Returns description of mock for toString() method Returns zero if references are equals otherwise non-zero for Comparable#compareTo(T other) method (see issue 184) Returns null for everything else
至此,最能說明問題仍然是源代碼,很想節約些篇幅,但實在是不行; 欣賞一下 ReturnsEmptyValues 的源代碼吧:
1 public class ReturnsEmptyValues implements Answer<Object>, Serializable { 2 3 private static final long serialVersionUID = 1998191268711234347L; 4 5 6 /* (non-Javadoc) 7 * @see org.mockito.stubbing.Answer#answer(org.mockito.invocation.InvocationOnMock) 8 */ 9 public Object answer(InvocationOnMock invocation) { 10 if (isToStringMethod(invocation.getMethod())) { 11 Object mock = invocation.getMock(); 12 MockName name = MockUtil.getMockName(mock); 13 if (name.isDefault()) { 14 return "Mock for " + MockUtil.getMockSettings(mock).getTypeToMock().getSimpleName() + ", hashCode: " + mock.hashCode(); 15 } else { 16 return name.toString(); 17 } 18 } else if (isCompareToMethod(invocation.getMethod())) { 19 //see issue 184. 20 //mocks by default should return 0 if references are the same, otherwise some other value because they are not the same. Hence we return 1 (anything but 0 is good). 21 //Only for compareTo() method by the Comparable interface 22 return invocation.getMock() == invocation.getArgument(0) ? 0 : 1; 23 } 24 25 Class<?> returnType = invocation.getMethod().getReturnType(); 26 return returnValueFor(returnType); 27 } 28 29 Object returnValueFor(Class<?> type) { 30 if (Primitives.isPrimitiveOrWrapper(type)) { 31 return Primitives.defaultValue(type); 32 //new instances are used instead of Collections.emptyList(), etc. 33 //to avoid UnsupportedOperationException if code under test modifies returned collection 34 } else if (type == Iterable.class) { 35 return new ArrayList<Object>(0); 36 } else if (type == Collection.class) { 37 return new LinkedList<Object>(); 38 } else if (type == Set.class) { 39 return new HashSet<Object>(); 40 } else if (type == HashSet.class) { 41 return new HashSet<Object>(); 42 } else if (type == SortedSet.class) { 43 return new TreeSet<Object>(); 44 } else if (type == TreeSet.class) { 45 return new TreeSet<Object>(); 46 } else if (type == LinkedHashSet.class) { 47 return new LinkedHashSet<Object>(); 48 } else if (type == List.class) { 49 return new LinkedList<Object>(); 50 } else if (type == LinkedList.class) { 51 return new LinkedList<Object>(); 52 } else if (type == ArrayList.class) { 53 return new ArrayList<Object>(); 54 } else if (type == Map.class) { 55 return new HashMap<Object, Object>(); 56 } else if (type == HashMap.class) { 57 return new HashMap<Object, Object>(); 58 } else if (type == SortedMap.class) { 59 return new TreeMap<Object, Object>(); 60 } else if (type == TreeMap.class) { 61 return new TreeMap<Object, Object>(); 62 } else if (type == LinkedHashMap.class) { 63 return new LinkedHashMap<Object, Object>(); 64 } else if ("java.util.Optional".equals(type.getName())) { 65 return JavaEightUtil.emptyOptional(); 66 } else if ("java.util.OptionalDouble".equals(type.getName())) { 67 return JavaEightUtil.emptyOptionalDouble(); 68 } else if ("java.util.OptionalInt".equals(type.getName())) { 69 return JavaEightUtil.emptyOptionalInt(); 70 } else if ("java.util.OptionalLong".equals(type.getName())) { 71 return JavaEightUtil.emptyOptionalLong(); 72 } else if ("java.util.stream.Stream".equals(type.getName())) { 73 return JavaEightUtil.emptyStream(); 74 } else if ("java.util.stream.DoubleStream".equals(type.getName())) { 75 return JavaEightUtil.emptyDoubleStream(); 76 } else if ("java.util.stream.IntStream".equals(type.getName())) { 77 return JavaEightUtil.emptyIntStream(); 78 } else if ("java.util.stream.LongStream".equals(type.getName())) { 79 return JavaEightUtil.emptyLongStream(); 80 } 81 82 //Let's not care about the rest of collections. 83 return null; 84 } 85 }
從上能夠看到全部列出的方法默認返回值的映射狀況,未涉及到的就是 null.
咱們還能夠關注一下另外一個 Answer: RETURN_SMART_NULL, 一樣是看相應實現類 ReturnsMoreEmptyValues 的註解
It's likely this implementation will be used by default by every Mockito 3.0.0 mock. Currently used only by Mockito.RETURNS_SMART_NULLS Current version of Mockito mocks by default use ReturnsEmptyValues Returns appropriate primitive for primitive-returning methods Returns consistent values for primitive wrapper classes (e.g. int-returning method returns 0 and Integer-returning method returns 0, too) Returns empty collection for collection-returning methods (works for most commonly used collection types) Returns empty array for array-returning methods Returns "" for String-returning method Returns description of mock for toString() method Returns non-zero for Comparable#compareTo(T other) method (see issue 184) Returns null for everything else
這仍是一個面向將來(Mockito 3.0.9) 的默認的 Answer, 它與 RETURNS_DEFAULTS 有所不一樣的是數組,字符串再也不爲 null, 而是空數組和空字符串。
咱們能夠做一個測試,前面的 MyClassTest 代碼,把構造 MyClass Mock 對象那一行從
1 MyClass myClass = Mockito.mock(MyClass.class);
1 MyClass myClass = Mockito.mock(MyClass.class, Mockito.withSettings() 2 .defaultAnswer(Answers.RETURNS_SMART_NULLS).verboseLogging());
咱們同時開啓了調用 Mock 方法時的詳細輸出,從新運行後,控制檯輸出
fields ---- integer: 0 array: null double: null string: null optional: null collection: null map: null methods ---- ############ Logging method invocation #1 on mock/spy ######## myClass.getInteger(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:31) has returned: "0" (java.lang.Integer) integer: 0 ############ Logging method invocation #2 on mock/spy ######## myClass.getArray(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:32) has returned: "[J@4009e306" ([J) array: [J@4009e306 ############ Logging method invocation #3 on mock/spy ######## myClass.getDouble(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:33) has returned: "0.0" (java.lang.Double) double: 0.0 ############ Logging method invocation #4 on mock/spy ######## myClass.getString(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:34) has returned: "" (java.lang.String) string: ############ Logging method invocation #5 on mock/spy ######## myClass.getOptional(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:35) has returned: "Optional.empty" (java.util.Optional) optional: Optional.empty ############ Logging method invocation #6 on mock/spy ######## myClass.getCollection(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:36) has returned: "[]" (java.util.LinkedList) ############ Logging method invocation #7 on mock/spy ######## myClass.getCollection(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:36) has returned: "[]" (java.util.LinkedList) collection: [], class java.util.LinkedList ############ Logging method invocation #8 on mock/spy ######## myClass.getMap(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:37) has returned: "{}" (java.util.HashMap) ############ Logging method invocation #9 on mock/spy ######## myClass.getMap(); invoked: -> at cc.unmi.MyClassTest.printDefaults(MyClassTest.java:37) has returned: "{}" (java.util.HashMap) map: {}, class java.util.HashMap
有所不一樣的也就是數組默認爲空,字符串默認爲空字符串,都再也不是 null 了。
另外,剩下的幾個 Answer,除了 CALL_REAL_METHODS 很容易理解(就是不 Mock 方法了)。其他三個
- RETURN_MOCKS(new ReturnsMocks())
- RETURNS_DEEP_STUBS(new ReturnsDeepStubs())
- RETURNS_SELF(new TriesToReturnSelf())
的具體用意待到有需求時再去扒它們吧。
類比於 Mockito, 我也大體測試了一下 JMockit,也有相似的行爲,不在此羅列了。