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

如何优雅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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 09:05:23