如何优雅Mock流畅接口IReceiver?解决Setup覆盖问题
模拟流畅接口IReceiver时的Setup覆盖问题及优化方案
我正在Mock流畅接口IReceiver,该接口的每个方法会向集合中添加请求,调用Get()时返回对应请求的结果。但由于Moq中最后一次Setup会覆盖之前的设置,无论调用AddRequest1().Get()还是AddRequest2().Get(),总是得到new List<int>{22}的结果。我已找到一种临时解决方案,想了解是否存在更优雅的实现方式,或者是否需要编写扩展来处理像receiverMock.Setup(_ => _.AddRequest1().Get()).Returns(new List<int>{11})这样的表达式,自动生成新的Mock并返回?
临时解决方案
[TestMethod] public void Test1() { var receiverMock = new Mock<IReceiver>(); var mock1 = new Mock<IReceiver>(); mock1.Setup(_ => _.Get()).Returns(new List<int>{11}); receiverMock.Setup(_ => _.AddRequest1()).Returns(mock1.Object); var mock2 = new Mock<IReceiver>(); mock2.Setup(_ => _.Get()).Returns(new List<int>{22}); receiverMock.Setup(_ => _.AddRequest2()).Returns(mock2.Object); var prog = new Program(receiverMock.Object); ... }
相关代码
原始测试代码
[TestMethod] public void Test1() { var receiverMock = new Mock<IReceiver>(); receiverMock.Setup(_ => _.AddRequest1().Get()).Returns(new List<int>{11}); receiverMock.Setup(_ => _.AddRequest2().Get()).Returns(new List<int>{22}); var prog = new Program(receiverMock.Object); ... }
接口与实现类
public interface IReceiver { IReceiver AddRequest1(); IReceiver AddRequest2(); IReceiver AddRequest3(); List<int> Get(); } public class Receiver: IReceiver { List<Request> _requests; public IReceiver AddRequest1() { _requests.Add("item1"); return this; } public IReceiver AddRequest2() { _requests.Add("item2"); return this; } public IReceiver AddRequest3() { _requests.Add("item3"); return this; } public List<int> Get() { List<int> results; // 根据_requests中的项获取对应值 return results; } }
更优雅的解决方案:自定义Moq扩展方法
Moq本身对流畅接口的链式调用支持有限,因为链式调用默认复用同一个Mock实例,导致后续Setup覆盖之前的配置。可以通过自定义扩展方法,自动解析链式表达式并创建关联的子Mock,简化代码写法:
扩展方法实现
public static class MoqExtensions { public static IReturnsResult<TMock> SetupChain<TMock, TResult>(this Mock<TMock> mock, Expression<Func<TMock, TResult>> expression, TResult result) where TMock : class { var visitor = new ChainExpressionVisitor(); visitor.Visit(expression.Body); var currentMock = mock; foreach (var methodCall in visitor.MethodCalls.Take(visitor.MethodCalls.Count - 1)) { var nextMock = new Mock<TMock>(); currentMock.Setup(Expression.Lambda<Func<TMock, TMock>>(methodCall, expression.Parameters)).Returns(nextMock.Object); currentMock = nextMock; } var finalMethod = visitor.MethodCalls.Last(); return currentMock.Setup(Expression.Lambda<Func<TMock, TResult>>(finalMethod, expression.Parameters)).Returns(result); } private class ChainExpressionVisitor : ExpressionVisitor { public List<MethodCallExpression> MethodCalls { get; } = new List<MethodCallExpression>(); protected override Expression VisitMethodCall(MethodCallExpression node) { MethodCalls.Add(node); return base.VisitMethodCall(node); } } }
使用方式
[TestMethod] public void Test1() { var receiverMock = new Mock<IReceiver>(); receiverMock.SetupChain(_ => _.AddRequest1().Get(), new List<int>{11}); receiverMock.SetupChain(_ => _.AddRequest2().Get(), new List<int>{22}); var prog = new Program(receiverMock.Object); // ...测试逻辑 }
这个扩展方法会自动拆解链式调用中的每个方法,为AddRequestX()创建对应的子Mock,并最终配置Get()的返回值,效果和手动创建多个Mock一致,但写法更简洁,贴合你最初期望的Setup语法。
内容的提问来源于stack exchange,提问作者theateist
相关产品推荐
相关产品推荐

