You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需逐个配置方法,将Mock实现委托给实际类?

解决方案

要实现「Mock接口时自动把所有方法委托给实际类,同时保留调用验证能力」,不用逐个手动Setup每个方法,有两种简便方式:

方法一:批量自动绑定接口方法到实际实例

通过反射+表达式树动态遍历接口的所有方法,自动为每个方法设置转发逻辑,一次性搞定所有方法的绑定:

using System.Linq.Expressions;

[Fact]
public void CheckCalcImpl()
{
    var realCalc = new Calculator();
    var mockCalc = new Mock<ICalculator>();

    // 遍历ICalculator的所有公共方法
    foreach (var method in typeof(ICalculator).GetMethods())
    {
        // 构建Setup的表达式:mock => mock.Method(It.IsAny<参数1>(), It.IsAny<参数2>())
        var paramExprs = method.GetParameters()
                               .Select(p => Expression.Call(typeof(It), "IsAny", new[] { p.ParameterType }))
                               .ToArray();
        var callExpr = Expression.Call(Expression.Parameter(typeof(ICalculator)), method, paramExprs);
        var setupLambda = Expression.Lambda<Action<ICalculator>>(callExpr, Expression.Parameter(typeof(ICalculator)));

        // 构建Returns的委托:(参数1, 参数2) => realCalc.Method(参数1, 参数2)
        var invokeParams = method.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
        var invokeExpr = Expression.Call(Expression.Constant(realCalc), method, invokeParams);
        var returnsDelegate = Expression.Lambda(invokeExpr, invokeParams).Compile();

        // 执行Setup和Returns绑定
        var setup = mockCalc.Setup(setupLambda);
        var returnsMethod = setup.GetType().GetMethod("Returns").MakeGenericMethod(method.ReturnType);
        returnsMethod.Invoke(setup, new[] { returnsDelegate });
    }

    var mock = mockCalc.Object;
    // 调用Mock方法,实际会执行Calculator的实现
    var result = mock.Add(1, 2);

    // 验证调用情况
    mockCalc.Verify(m => m.Add(1, 2), Times.Once);
}

这段代码会自动处理接口的所有方法,不管数量多少,都不用手动写重复的Setup语句。

方法二:自定义Moq默认值提供器(复用性更强)

如果多个测试都需要这个功能,可以自定义Moq的默认值提供器,让未手动Setup的方法自动转发到实际实例,同时保留Mock的调用记录能力:

using Castle.DynamicProxy;
using Moq;
using Moq.DefaultValueProviders;

public class RealInstanceProvider : DefaultValueProvider
{
    private readonly object _realInstance;
    private readonly ProxyGenerator _proxyGenerator = new ProxyGenerator();

    public RealInstanceProvider(object realInstance)
    {
        _realInstance = realInstance;
    }

    protected override object GetDefaultValue(Type type, Mock mock)
    {
        if (type.IsInterface)
        {
            return _proxyGenerator.CreateInterfaceProxyWithTarget(type, _realInstance, new MockInvocationRecorder(mock));
        }
        return base.GetDefaultValue(type, mock);
    }
}

public class MockInvocationRecorder : IInterceptor
{
    private readonly Mock _mock;

    public MockInvocationRecorder(Mock mock)
    {
        _mock = mock;
    }

    public void Intercept(IInvocation invocation)
    {
        // 先执行实际实例的方法逻辑
        invocation.Proceed();
        // 把调用记录到Mock中,方便后续验证
        _mock.Invocations.Add(new Invocation(invocation.Proxy, invocation.Method, invocation.Arguments));
    }
}

然后在测试中直接使用:

[Fact]
public void CheckCalcImplWithCustomProvider()
{
    var realCalc = new Calculator();
    var mockCalc = new Mock<ICalculator>
    {
        DefaultValueProvider = new RealInstanceProvider(realCalc)
    };

    var mock = mockCalc.Object;
    // 调用任意接口方法都会自动转发到realCalc
    var result = mock.Add(1, 2);
    // 正常验证调用情况
    mockCalc.Verify(m => m.Add(1, 2), Times.Once);
}

这种方式适合需要重复使用的场景,一次定义,所有测试都能直接复用。

注意事项

  • 确保项目已安装Moq和Castle.Core(Castle.DynamicProxy是Moq依赖的库,安装Moq时会自动附带)
  • 如果接口包含异步方法,需要在批量绑定或拦截器中额外处理Task类型的返回值,保证异步逻辑正确执行

内容的提问来源于stack exchange,提问作者mcse3010

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 14:25:58