在 Java 测试中使用 Mockito 有段时日了,以前只是想当然的认为 Mock 的对象属性值和方法返回值都是依据同样的规则。基本类型是 0, 0.0, 或 false, 对象类型都是 null, Mock 对象的默认返回值也应该是一样的。直到最近有一天,有一个返回 Optional<String> 类型的方法,由于忘记对该方法打桩,意外的发现它返回的不是 null, 而 Optional.empty(), 因此才意识到此处定有蹊跷。着实有必要用代码验证一下 Mockito 是怎么决定属性及方法的各种返回类型的默认值的。

此次测试所用的 Mockito 版本是 mockito-core-2.12.0.

于是创建了下面一个类 MyClass 用于生成 Mock 对象,选取了一些典型的数据类型, 包括 int, Double, String, long[], Optional<String>, Collection<String>, Map<String, String>, 同时测试 Mock 对象默认的属性值与方法默认返回值。

该类的完整代码如下:

 package cc.unmi;

 import java.util.Collection;
import java.util.Map;
import java.util.Optional; public class MyClass {
public int integer;
public Double aDouble;
public String string;
public long[] array;
public Optional<String> optional;
public Collection<String> collection;
public Map<String, String> map; public int getInteger() {
return 99;
} public long[] getArray() {
return new long[]{0};
} public Double getDouble() {
return 9.9;
} public String getString() {
return "hello";
} public Optional<String> getOptional() {
return null;
} public Collection<String> getCollection() {
return null;
} public Map<String, String> getMap() {
return null;
}
}
为了认识到调用 Mock 对象时默认情况下不会调用实际方法实现,我们故意让上面的方法返回一些乱七八糟的值。 测试类 MyClassTest 的代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package cc.unmi; import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class)
public class MyClassTest { @Test
public void watchMockedClass() {
MyClass myClass = Mockito.mock(MyClass.class);
printDefaults(myClass);
} private void printDefaults(MyClass myClass) {
System.out.println("fields ---- ");
System.out.println("integer: " + myClass.integer);
System.out.println("array: " + myClass.array);
System.out.println("double: " + myClass.aDouble);
System.out.println("string: " + myClass.string);
System.out.println("optional: " + myClass.optional);
System.out.println("collection: " + myClass.collection);
System.out.println("map: " + myClass.map); System.out.println("\nmethods ---- ");
System.out.println("integer: " + myClass.getInteger());
System.out.println("array: " + myClass.getArray());
System.out.println("double: " + myClass.getDouble());
System.out.println("string: " + myClass.getString());
System.out.println("optional: " + myClass.getOptional());
System.out.println("collection: " + myClass.getCollection() + ", " + myClass.getCollection().getClass());
System.out.println("map: " + myClass.getMap() + ", " + myClass.getMap().getClass());
}
}

执行上面的代码输出如下:

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。但是对于方法默认返回值就不一样了,从上面我们看到

  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 对象方法的默认返回值的呢?

通常的,我们创建一个 Mock 对象都是简单的调用 Mockito 的如下方法:

 public static <T> T mock(Class<T> classToMock) {
return mock(classToMock, withSetting());
}

再看 withSetting() 方法:

 public static MockSetting withSetting() {
return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
}

绕了一圈,基实我们默认采用的 Mock 对象的方式其实就是如下:

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 的源代码吧:

 public class ReturnsEmptyValues implements Answer<Object>, Serializable {

     private static final long serialVersionUID = 1998191268711234347L;

     /* (non-Javadoc)
* @see org.mockito.stubbing.Answer#answer(org.mockito.invocation.InvocationOnMock)
*/
public Object answer(InvocationOnMock invocation) {
if (isToStringMethod(invocation.getMethod())) {
Object mock = invocation.getMock();
MockName name = MockUtil.getMockName(mock);
if (name.isDefault()) {
return "Mock for " + MockUtil.getMockSettings(mock).getTypeToMock().getSimpleName() + ", hashCode: " + mock.hashCode();
} else {
return name.toString();
}
} else if (isCompareToMethod(invocation.getMethod())) {
//see issue 184.
//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).
//Only for compareTo() method by the Comparable interface
return invocation.getMock() == invocation.getArgument(0) ? 0 : 1;
} Class<?> returnType = invocation.getMethod().getReturnType();
return returnValueFor(returnType);
} Object returnValueFor(Class<?> type) {
if (Primitives.isPrimitiveOrWrapper(type)) {
return Primitives.defaultValue(type);
//new instances are used instead of Collections.emptyList(), etc.
//to avoid UnsupportedOperationException if code under test modifies returned collection
} else if (type == Iterable.class) {
return new ArrayList<Object>(0);
} else if (type == Collection.class) {
return new LinkedList<Object>();
} else if (type == Set.class) {
return new HashSet<Object>();
} else if (type == HashSet.class) {
return new HashSet<Object>();
} else if (type == SortedSet.class) {
return new TreeSet<Object>();
} else if (type == TreeSet.class) {
return new TreeSet<Object>();
} else if (type == LinkedHashSet.class) {
return new LinkedHashSet<Object>();
} else if (type == List.class) {
return new LinkedList<Object>();
} else if (type == LinkedList.class) {
return new LinkedList<Object>();
} else if (type == ArrayList.class) {
return new ArrayList<Object>();
} else if (type == Map.class) {
return new HashMap<Object, Object>();
} else if (type == HashMap.class) {
return new HashMap<Object, Object>();
} else if (type == SortedMap.class) {
return new TreeMap<Object, Object>();
} else if (type == TreeMap.class) {
return new TreeMap<Object, Object>();
} else if (type == LinkedHashMap.class) {
return new LinkedHashMap<Object, Object>();
} else if ("java.util.Optional".equals(type.getName())) {
return JavaEightUtil.emptyOptional();
} else if ("java.util.OptionalDouble".equals(type.getName())) {
return JavaEightUtil.emptyOptionalDouble();
} else if ("java.util.OptionalInt".equals(type.getName())) {
return JavaEightUtil.emptyOptionalInt();
} else if ("java.util.OptionalLong".equals(type.getName())) {
return JavaEightUtil.emptyOptionalLong();
} else if ("java.util.stream.Stream".equals(type.getName())) {
return JavaEightUtil.emptyStream();
} else if ("java.util.stream.DoubleStream".equals(type.getName())) {
return JavaEightUtil.emptyDoubleStream();
} else if ("java.util.stream.IntStream".equals(type.getName())) {
return JavaEightUtil.emptyIntStream();
} else if ("java.util.stream.LongStream".equals(type.getName())) {
return JavaEightUtil.emptyLongStream();
} //Let's not care about the rest of collections.
return null;
}
}

从上可以看到所有列出的方法默认返回值的映射情况,未涉及到的就是 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  对象那一行从

 MyClass myClass = Mockito.mock(MyClass.class);
 MyClass myClass = Mockito.mock(MyClass.class, Mockito.withSettings()
.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,也有类似的行为,不在此罗列了。

Mockito 中被 Mocked 的对象属性及方法的默认值的更多相关文章

  1. getSelection、range 对象属性,方法理解,解释

    网上转了一圈发现没有selection方面的解释,自己捣鼓下 以这段文字为例子.. <p><b>法国国营铁路公司(SNCF)20日承认,</b>新订购的2000列火 ...

  2. Javascript对象属性与方法汇总

    Javascript对象属性与方法汇总 发布时间:2015-03-06 编辑:www.jquerycn.cn 详细介绍下,javascript对象属性与对象方法的相关知识,包括javascript字符 ...

  3. Js基础知识7-JavaScript所有内置对象属性和方法汇总

    对象什么的,程序员可是有很多呢... JS三大对象 对象,是任何一个开发者都无法绕开和逃避的话题,她似乎有些深不可测,但如此伟大和巧妙的存在,一定值得你去摸索.发现.征服. 我们都知道,JavaScr ...

  4. JavaScript 访问对象属性和方法及区别

    这篇文章主要介绍了浅析JavaScript访问对象属性和方法及区别的相关资料,仅供参考 属性是一个变量,用来表示一个对象的特征,如颜色.大小.重量等:方法是一个函数,用来表示对象的操作,如奔跑.呼吸. ...

  5. JS枚举对象属性的方法及其区别

    愉快的中秋节要过去了,国庆倒计时两个周!!! 闲话不多说,那今天我们来看一看JS中枚举对象属性的方法有哪些以及他们的区别 首先在JS里面枚举对象属性一共有三种方法 for in: 会遍历对象中所有的可 ...

  6. js中的数据类型及常用属性和方法

    JavaScript 字符串 字符串(或文本字符串)是一串字符(比如 "Bill Gates").字符串被引号包围.您可使用单引号或双引号您可以在字符串内使用引号,只要这些引号与包 ...

  7. js object 对象 属性和方法的使用

    //object 对象 属性和方法的使用 var person = new Object(); person.name="张海"; person.age="; perso ...

  8. 转: JavaScript 获取对象属性和方法

    一.获取对象属性和方法 Object.keys()for in 返回对象的可枚举属性和方法的名称数组. Object.getOwnPropertyNames() 返回的数组的所有属性(可枚举或不可枚举 ...

  9. JavaScript中易混淆的DOM属性及方法对比

    JavaScript中易混淆的DOM属性及方法对比 ParentNode.children VS Node.prototype.childNodes ParentNode.children:该属性继承 ...

随机推荐

  1. SpringBoot详细研究-02数据访问

    Springboot对数据访问部分提供了非常强大的集成,支持mysql,oracle等传统数据库的同时,也支持Redis,MongoDB等非关系型数据库,极大的简化了DAO的代码,尤其是Spring ...

  2. 【Vue实战之路】一、Vue-cli入门及Vue工程目录全解。

    全面的Vue-cli学习,这一篇就够了! 一.下载 使用vue-cli前,需先安装node.js,node的安装就不赘述,不过在此需要注意: 1. node版本需在4.x以上,首推6.x以上版本(no ...

  3. 循序渐进学.Net Core Web Api开发系列【13】:中间件(Middleware)

    系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍如 ...

  4. 初识MYSQL2

    mysql的配置 MySql默认的端口号是3306 默认字符集的设置 在mysql的安装目录,会看到my.ini文件! my.ini文件介绍 01.default-character-set=utf8 ...

  5. Bzoj3122:多项式BSGS

    根据鸽笼原理,在p次后一定循环,一眼BSGS.发现他给的函数是个一次函数,一次函数有什么性质呢?f(f(x))还是一次函数,这样就能做了.首先我们暴力预处理出f(f(f(x)))......sqrt( ...

  6. BZOJ.3052.[WC2013]糖果公园(树上莫队 带修改莫队)

    题目链接 BZOJ 当然哪都能交(都比在BZOJ交好),比如UOJ #58 //67376kb 27280ms //树上莫队+带修改莫队 模板题 #include <cmath> #inc ...

  7. 使用pytorch构建神经网络的流程以及一些问题

    使用PyTorch构建神经网络十分的简单,下面是我总结的PyTorch构建神经网络的一般过程以及我在学习当中遇到的一些问题,期望对你有所帮助. PyTorch构建神经网络的一般过程 下面的程序是PyT ...

  8. 网站性能工具-YSlow的23个规则-网站性能优化

    1. 减少HTTP请求次数 合并图片.CSS.JS,改进首次访问用户等待时间. 2. 使用CDN 就近缓存==>智能路由==>负载均衡==>WSA全站动态加速 3. 避免空的src和 ...

  9. 使用java中replaceAll方法替换字符串中的反斜杠

    今天在项目中使用java中replaceAll方法将字符串中的反斜杠("\")替换成空字符串(""),结果出现如下的异常: java.util.regex.Pa ...

  10. Object [object Object] has no method 'live'

    用了2个jquery的2个文件: <script src="~/Scripts/jquery-1.10.2.js"></script> <script ...