如何无需逐个配置方法,将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
相关产品推荐
相关产品推荐

