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

本文探讨Mockito的实现原理,重点解析Mockito.when(...).thenReturn(...)的功能,通过Java动态代理和CGLIB实现模拟对象的创建。建议读者具备Java动态代理和CGLIB的基础知识,以便更好地理解Mockito如何在单元测试中发挥作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        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));
    }
}





评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值