Mockito 实现原理(2):spy 的原理

背景

上一篇讲了 mock 的基本原理,这一篇简单谈谈 spy

可以这么理解这两者之间的区别:

  • mock 完全就是为了欺骗编译器。你 mock 的对象完全就是一个空壳,返回的结果要么就是类型默认值(比如 boolean 类型结果就返回 falseint 类型就返回 0),要么就是你自己指定。总之,这个 mock 对象不会真正执行它的方法,也没有相应的内部状态;
  • spy 则获取一个“真实”的对象,也就是说,完全可以拿来当一个正常的对象来用,有内部状态,也返回正确结果。唯一的不同是,你也可以指定让它返回你想要的结果;

spy 的一个典型用法就是,你想测试某个对象 obj 的某个方法 A,但 A 会调用它的另一个方法 B,而你想把 B mock 掉。这时候就可以基于 obj 创建一个 spiedObj,然后把它的 B 方法 mock 掉就可以了。

(注:本文基于 Mockito 4.6.1 源码

spy 和 mock 在实现上的不同

不同点 1:默认返回值

可以看到,在创建 spy 对象时,Mockito 会指定 CALLS_REAL_METHODS 作为 defaultAnswer

// java/org/mockito/Mockito.java
// 第 2063 行
public static <T> T spy(T object) {
	return MOCKITO_CORE.mock(
		    (Class<T>) object.getClass(),
		    // 注意这里
		    withSettings().spiedInstance(object).defaultAnswer(CALLS_REAL_METHODS));
	}

这里的 CALLS_REAL_METHODS 并非一个字符串,而是某个对象。总之就是,根据这个配置,在对 mock 对象进行方法调用时,默认会执行真正的方法,而不是返回 mock 结果。

对比一下,创建 mock 对象时,会设置 RETURNS_DEFAULTS 作为 defaultAnswer

// java/org/mockito/Mockito.java
// 第 1895 行
public static <T> T mock(Class<T> classToMock) {
    return mock(classToMock, withSettings());
}

// ...

// 第 3251 行
public static MockSettings withSettings() {
	// 注意这里
    return new MockSettingsImpl().defaultAnswer(RETURNS_DEFAULTS);
}

这里的 RETURNS_DEFAULTS 的实现,基本可以理解为返回类型默认值(比如 boolean 类型结果就返回 falseint 类型就返回 0)。

不同点 2:内部状态

当然,只执行真实方法还不够,还需要有内部状态才行。

比如,下面的代码:

List<String> list = new ArrayList<>();
list.add("123");
list.add("456");

List spiedList = Mockito.spy(list);

既然 list 对象中添加了 "123""456"这两个元素,那么 spiedList 中也得有这两个元素才行。

Mockito 中相关实现代码如下,说白了就是会把原对象的字段都复制过来:

// java/org/mockito/internal/util/MockUtil.java
// 第 41 行
T mock;
if (spiedInstance != null) {
	// spy 会走这里
    mock =
            mockMaker
                    .createSpy(settings, mockHandler, (T) spiedInstance)
                    .orElseGet(
                            () -> {
                            	// 首先创建一个 mock
                            	// 可以看到,和下面创建普通 mock 的方法是一模一样的
                                T instance = mockMaker.createMock(settings, mockHandler);
                                // 注意这一步,会把原对象中的字段都复制过来
                                new LenientCopyTool().copyToMock(spiedInstance, instance);
                                return instance;
                            });
} else {
	// 普通的 mock 会走这里
    mock = mockMaker.createMock(settings, mockHandler);
}

小结

可以看到,在实现上,spymock 是非常相近的。

如果你想创建 spy,实际上也是先创建了一个 mock(只不过 settings 不同,包括 settings 里设置的默认返回结果方式不同),然后把原对象的字段复制过去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值