Mockito 中被 Mocked 的對象屬性及方法的默認值

在 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

  1. int 類型方法默認返回 0
  2. long[] 類型方法默認返回 null
  3. Double 類型方法默認返回 0.0
  4. string 類型方法默認返回 null
  5. Optional<String> 類型方法默認返回 Optional.empty
  6. Collection<String> 類型方法默認返回 new LinkedList<String>(0)
  7. 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 中定義了以下設定方法默認返回值的選項

  1. RETURN_DEFAULTS(new GloballyConfiguredAnswer())  -- 基本對應到 ReturnsEmptyValues 實現
  2. RETURNS_SMART_NULLS(new ReturnsSmartNulls())  -- 最後對應到 ReturnsMoreEmptyValues 實現
  3. RETURN_MOCKS(new ReturnsMocks())
  4. RETURNS_DEEP_STUBS(new ReturnsDeepStubs())
  5. CALL_REAL_METHODS(new CallsRealMethods())
  6. 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,也有相似的行爲,不在此羅列了。

相關文章
相關標籤/搜索