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

使用FakeItEasy无法设置列表类型返回值的问题求助

问题

我正在测试Web API控制器,但无法解决列表类型返回值的伪造问题。调试时发现ClassSql.ReadAllClassesAsync()的输出未被伪造,但测试单个值的ClassSql.ReadClassAsync()方法时伪造功能正常,相关测试都能通过。

控制器代码:

public class ClassController : ControllerBase
{
    private readonly IClassSqlService ClassSql;
    private readonly ILogger<ClassController> Logger;

    public ClassController(
            IConfiguration config,
            ILogger<ClassController> logger,
            IDataAccessAbstractFactory<IClassSqlService> classSql)
    {
        string SMString = config.GetValue<string>(DataAccessName.SMCnx)!;
        Logger = logger;
        ClassSql = classSql.Create(SMString);
    }

    [HttpGet]
    [ProducesResponseType(200, Type = typeof(List<ClassModel>))]
    public async Task<ActionResult<List<ClassModel>>> ReadAllClasses(
            string? academic_year = null)
    {
        List<ClassModel> output;

        if(academic_year is not null)
        {
            bool isIdValid = int.TryParse(academic_year, out int year);

            if (isIdValid is false)
                return BadRequest();

            output = await ClassSql.ReadAllClassesByAcademicYearAsync(academic_year);
        }
        else
        {
            output = await ClassSql.ReadAllClassesAsync();
        }

        if (output.Any() is false)
            return NotFound();

        return Ok(output);
    }
}

单元测试代码:

public class ClassControllerTests
{
    private readonly IClassSqlService ClassSql;
    private readonly ClassController sut;

    public ClassControllerTests()
    {
        var config = A.Fake<IConfiguration>();
        var classSql = A.Fake<IDataAccessAbstractFactory<IClassSqlService>>();
        var logger = A.Fake<ILogger<ClassController>>();

        sut = new ClassController(config, logger, classSql);
        ClassSql = classSql.Create(string.Empty);
    }

    [Fact]
    public async Task ReadAllClassesShouldReturnOk()
    {
        // arrange
        var classes = A.CollectionOfFake<ClassModel>(5);
        A.CallTo(() => ClassSql.ReadAllClassesAsync()).Returns(classes.ToList());

        // action
        var result = await sut.ReadAllClasses();

        // assert
        result.Result.Should().NotBeNull();
        result.Result.Should().BeOfType(typeof(OkObjectResult));
        result.Value.Should().NotBeEmpty();
    }
}

我已经查阅FakeItEasy文档并参照伪造集合的示例操作,但问题仍未解决,希望得到帮助。

解决方案

核心问题是:控制器构造函数中调用classSql.Create(SMString)时使用的是从配置获取的连接字符串,而测试代码中调用classSql.Create(string.Empty)得到的是另一个Fake实例。FakeItEasy默认会根据调用参数的不同生成不同的Fake对象,导致控制器实际使用的IClassSqlService和你在测试中配置伪造调用的实例不是同一个,伪造的返回值自然不会生效。

修改测试代码,确保控制器和测试使用同一个Fake的IClassSqlService实例:

public class ClassControllerTests
{
    private readonly IClassSqlService _classSql;
    private readonly ClassController _sut;

    public ClassControllerTests()
    {
        var config = A.Fake<IConfiguration>();
        // 预先创建Fake的IClassSqlService实例
        _classSql = A.Fake<IClassSqlService>();
        var classSqlFactory = A.Fake<IDataAccessAbstractFactory<IClassSqlService>>();
        var logger = A.Fake<ILogger<ClassController>>();

        // 配置工厂:无论传入什么连接字符串,都返回预先创建的Fake实例
        A.CallTo(() => classSqlFactory.Create(A<string>._)).Returns(_classSql);

        // 可选:如果需要验证连接字符串的读取,可以配置config的返回值
        A.CallTo(() => config.GetValue<string>(DataAccessName.SMCnx)).Returns("test_connection_string");

        _sut = new ClassController(config, logger, classSqlFactory);
    }

    [Fact]
    public async Task ReadAllClassesShouldReturnOk()
    {
        // arrange
        var fakeClasses = A.CollectionOfFake<ClassModel>(5).ToList();
        A.CallTo(() => _classSql.ReadAllClassesAsync()).Returns(fakeClasses);

        // action
        var result = await _sut.ReadAllClasses();

        // assert
        result.Result.Should().BeOfType<OkObjectResult>();
        result.Value.Should().BeEquivalentTo(fakeClasses);
    }
}

关键修改点:

  • 提前创建Fake的IClassSqlService实例,而不是通过工厂的Create方法获取
  • 配置Fake工厂的Create方法,使用A<string>._匹配任意字符串参数,确保无论控制器传入什么连接字符串,都返回同一个Fake实例
  • (可选)配置config的返回值,模拟真实环境中连接字符串的读取

这样修改后,控制器使用的IClassSqlService和测试中配置伪造调用的实例是同一个,伪造的返回值会正常生效,测试就能通过了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:38:11