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

如何用Moq设置返回多元素列表的单元测试?

问题描述

在使用Moq编写单元测试时,希望Mock GetMyRequest 方法,让它返回包含2个元素的响应列表,再通过Task.WhenAll处理这些异步任务。但使用SetupSequence后,最终myResponses始终是单元素列表,无法得到预期的多元素结果。

业务方法代码

public void MyMethod()
{
    ProductService = new Mock<IProductService>();
    var myResponse = myStringList.Select(async x => await _myService.GetMyRequest(x, null, null)).ToList();

    // my response type -> List<Task<ResponseModel>>
    var myResponses = await Task.WhenAll(myResponse);
}

单元测试代码

public async Task InitializeAsync(){
    ServiceResponse = Fixture.Build<MyModel>().CreateMany(2).ToList();
    MyService = new Mock<IProductService>();
    MyService.SetupSequence(service => service.GetMyRequest(It.IsAny<string>(), null, null))
                      .ReturnsAsync(ServiceResponse[0])
                      .ReturnsAsync(ServiceResponse[1]);
}

解决方法

1. 修复业务类的依赖注入问题

业务方法中不能在内部创建Mock实例,_myService必须通过构造函数注入,这样测试时才能将Mock实例传入并生效:

public class MyBusinessClass
{
    private readonly IProductService _myService;
    private readonly List<string> _myStringList;

    // 通过构造函数注入依赖
    public MyBusinessClass(IProductService myService, List<string> myStringList)
    {
        _myService = myService;
        _myStringList = myStringList;
    }

    // 方法改为async Task,适配内部await逻辑
    public async Task MyMethod()
    {
        var myResponseTasks = _myStringList.Select(x => _myService.GetMyRequest(x, null, null)).ToList();
        var myResponses = await Task.WhenAll(myResponseTasks);
        
        // 后续处理逻辑
    }
}

2. 确保myStringList有足够多的元素

Task.WhenAll返回的列表长度等于myStringList的元素数量,如果列表只有1个元素,自然只能得到单元素结果。测试时要确保传入的myStringList包含对应数量的元素(比如需要2个响应,就传入2个字符串元素)。

3. 用队列替代SetupSequence实现多返回值

SetupSequence仅能按顺序设置固定次数的返回值,当调用次数超过设置次数时会返回默认值。改用队列存储响应,每次调用自动取出下一个元素,更灵活可靠:

public async Task MyMethod_ReturnsMultipleResponses_Test()
{
    // Arrange
    var fixture = new Fixture();
    var serviceResponses = fixture.Build<MyModel>().CreateMany(2).ToList();
    // 测试用字符串列表需包含2个元素
    var testStringList = new List<string> { "product1", "product2" };
    
    var myServiceMock = new Mock<IProductService>();
    var responseQueue = new Queue<MyModel>(serviceResponses);
    
    // 设置每次调用GetMyRequest时,从队列取出下一个响应返回
    myServiceMock.Setup(s => s.GetMyRequest(It.IsAny<string>(), null, null))
                 .ReturnsAsync(() => responseQueue.Dequeue());

    // 创建业务类实例,注入Mock和测试列表
    var businessClass = new MyBusinessClass(myServiceMock.Object, testStringList);

    // Act
    await businessClass.MyMethod();

    // Assert
    // 验证GetMyRequest被调用了2次
    myServiceMock.Verify(s => s.GetMyRequest(It.IsAny<string>(), null, null), Times.Exactly(2));
}

关键说明

  • 核心问题是业务方法内自行创建Mock,导致测试中设置的Mock实例未被使用,修复依赖注入后Mock的设置才能生效。
  • 队列方式比SetupSequence更适合处理多次调用的返回值,尤其是当需要模拟大量响应时。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 05:15:23