Mockito实现原理探析 -- Mockito.when(...).thenReturn(...)的一个简化实现

本文探讨Mockito的实现原理,重点解析Mockito.when(...).thenReturn(...)的功能,通过Java动态代理和CGLIB实现模拟对象的创建。建议读者具备Java动态代理和CGLIB的基础知识,以便更好地理解Mockito如何在单元测试中发挥作用。
        Mockito是一套非常强大的测试框架,被广泛的应用于Java程序的unit test中,而在其中被使用频率最高的恐怕要数"Mockito.when(...).thenReturn(...)"了。那这个用起来非常便捷的"Mockito.when(...).thenReturn(...)",其背后的实现原理究竟为何呢?为了一探究竟,笔者实现了一个简单的MyMockito,也提供了类似的"MyMockito.when(...).thenReturn(...)"支持。当然这个实现只是一个简单的原型,完备性和健壮性上都肯定远远不及mockito(比如没有annotation支持和thread safe),但应该足以帮助大家理解"Mockito.when(...).thenReturn(...)"的核心实现原理。

       在阅读以下代码之前,希望读者能简单的了解一下Java动态代理和cglib,没接触过的至少应该写两个简单的小程序感受一下(这篇博文中就提供了一些简短的示例)。cglib是一个强大的高性能的代码生成包,被许多AOP的框架所使用,Mockito也使用了这个库,我们的MyMockito自然也不例外。


P.S.:

示例代码中还使用了lombok,省去了构造函数的编写。



import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lombok.Data;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

@Data
class MethodInfo {
    private final MyCGLibInterceptor interceptor;
    private final Method method;
    private final Object[] args;
    
    @Override
    public String toString() {
        return "{interceptor: " + interceptor + ", Method: " + method + ", args: " + Arrays.toString(args) + "}";
    }
    
    @Override
    public boolean equals(final Object other) {
        if (other instanceof MethodInfo) {
            final MethodInfo otherMethodInfo = (MethodInfo)other;
            return interceptor.equals(otherMethodInfo.interceptor) && method.equals(otherMethodInfo.method) && Arrays.equals(args, otherMethodInfo.args);
        }
        
        return false;
    }
    
    @Override
    public int hashCode() {
        return interceptor.hashCode() + method.hashCode() + Arrays.hashCode(args);
    }
}

@Data
class MockInjector {
    private final MethodInfo methodInfo;
    public void thenReturn(final Object mockResult) {
        MyMockito.MOCKED_METHODS.put(methodInfo, mockResult);
    }
}

class MyCGLibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable {        
        final MethodInfo key = new MethodInfo(this, method, args);
        final boolean hasMocked = MyMockito.MOCKED_METHODS.containsKey(key);
        if (!hasMocked) {
            // When called for the first time (by MyMockito.when(...)),
            // return a MethodInfo object used as key,
            // so that the later MethodInfo.thenReturn(...) will use this key
            // to insert mock result into the MyMockito.MOCKED_METHODS.
            System.out.println("Initializing the mock for " + key.toString());
            return key;
        } else {
            // Now that MyMockito.MOCKED_METHODS already contains the mock result
            // for this method call, just return the mock result.
            System.out.println("Returns the mock result:");
            return MyMockito.MOCKED_METHODS.get(key);
        }
    }
    
    public Object getInstance(final Class<?> t) {
        final Enhancer enhancer = new Enhancer();  
        enhancer.setSuperclass(t);  
        enhancer.setCallback(this);  
        return enhancer.create();
    }
}

public class MyMockito {
    public static final Map<MethodInfo, Object> MOCKED_METHODS = new HashMap<MethodInfo, Object>();
    
    public static MockInjector when(Object methodCall) {
        return new MockInjector((MethodInfo)methodCall);
    }
    
    private static MyCGLibInterceptor getInterceptor() {
        return new MyCGLibInterceptor();
    }

    public static void main(String[] args) {
        final List<String> myMockList1 = 
                (List<String>)getInterceptor().getInstance(List.class); 
        final List<String> myMockList2 = 
                (List<String>)getInterceptor().getInstance(List.class); 
        final Map<Integer, String> myMockMap = 
                (Map<Integer, String>)getInterceptor().getInstance(Map.class); 
        
        MyMockito.when(myMockList1.get(0)).thenReturn("Hello, I am James");
        MyMockito.when(myMockList1.get(2)).thenReturn("Hello, I am Billy");
        MyMockito.when(myMockList2.get(0)).thenReturn("Hello, I am Tom");
        MyMockito.when(myMockMap.get(10)).thenReturn("Hello, I am Bob");
        
        System.out.println("myMockList1.get(0) = " + myMockList1.get(0));
        System.out.println("myMockList1.get(2) = " + myMockList1.get(2));
        System.out.println("myMockList2.get(0) = " + myMockList2.get(0));
        System.out.println("myMockMap.get(10) = " + myMockMap.get(10));
    }
}





### 关于 Mockito `when().thenReturn()` 方法失效的原因分析 当使用 Mockito 的 `when().thenReturn()` 方法时,如果发现其行为未按预期工作,则可能涉及以下几个常见原因: #### 1. **对象实例不匹配** 如果传递给 `when()` 和实际调用的对象不是同一个实例,即使它们看起来相同,`thenReturn()` 也不会生效。这是因为 Mockito 使用的是引用比较而非值比较[^1]。 #### 2. **未正确初始化 Mock 对象** 如果目标对象没有通过 `Mockito.mock(Class<T> clazz)` 或者注解方式(如 `@Mock`)创建,而是直接使用真实类的新实例,则无法应用 stubbing 行为[^3]。 #### 3. **Stubbing 应用于 Real Object 而非 Mock/Spy** 当尝试对一个真实的对象进行 stubbing 时,如果没有将其转换成 Spy 实例,那么该操作不会起作用。Spy 是一种部分 mock 技术,允许保留原始实现的同时覆盖某些方法的行为[^2]。 #### 解决方案代码示例 以下是针对上述情况的一个解决方案示例: ```java import static org.mockito.Mockito.*; public class TestClass { public static void main(String[] args) { // 创建一个接口的 Mock 实例 SomeInterface mockObject = mock(SomeInterface.class); // 正确设置 Stubbing when(mockObject.someMethod("test")).thenReturn("mocked result"); // 验证 Stubbing 是否有效 System.out.println(mockObject.someMethod("test")); // 输出应为 "mocked result" // 如果需要部分 Mock (即保持其他方法的真实逻辑),可以使用 Spy SomeRealClass spiedObject = spy(new SomeRealClass()); // 设置特定方法的返回值 doReturn(10).when(spiedObject).getNumber(); // 测试被 Spied 的方法是否按照设定返回值 System.out.println(spiedObject.getNumber()); // 输出应为 10 } } interface SomeInterface { String someMethod(String input); } class SomeRealClass { int getNumber() { return 5; } } ``` #### 4. **静态方法或 Final 类/方法** 默认情况下,Mockito 不支持对静态方法或者 final 类/方法进行 mocking。这可能导致 `when().thenReturn()` 失效。要解决此问题,可考虑升级到 Mockito 2.x 及以上版本并启用扩展功能[^1]。 --- ### 总结 为了确保 `when().thenReturn()` 生效,请确认以下几点: - 目标对象已被适当声明为 Mock 或 Spy; - 执行 stubbing 前后所使用的对象实例一致; - 若需处理静态/final 成员,则引入额外工具库(例如 PowerMock)作为补充。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值