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

.NET 8主构造函数参数在单元测试中注入失效问题

.NET 8主构造函数在单元测试中依赖为空的原因分析

问题场景

在传统ASP.NET服务层的xUnit+Substitute单元测试场景中,常规构造函数注入依赖的代码运行正常。但在.NET 8(C# 12)项目里,使用Rider提示的主构造函数参数写法实现AppService后,单元测试中注入的IAppRepository对象在Get方法中为空,但实际运行时却正常。

代码示例

传统构造函数实现(Service.cs)

public class AppService : IAppService
{
  private readonly IAppRepository _appRepository;
  public AppService(IAppRepository appRepository) 
  {
    this._appRepository = appRepository;
  }

  public App Get(int id) 
  {
    // 业务逻辑代码
    return _appRepository.Get(id);
  }
}

单元测试代码(AppServiceTests.cs)

public class AppServiceTests
{
  public void Test()
  {
    IAppRepository mockAppRepository = Substitute.For<IAppRepository>();
    // Mock逻辑设置
    AppService service = new AppService(mockAppRepository);
    // 断言逻辑
  }
}

主构造函数参数实现(AppService.cs)

public class AppService(IAppRepository appRepository) : IAppService
{
  // 无需定义私有字段
  public App Get(int id)
  {
    return appRepository.Get(id);
  }
}

差异原因分析

  1. 主构造函数的合成字段特性
    C# 12主构造函数允许直接在类中使用构造参数,编译器会自动生成一个私有合成字段(如<appRepository>__BackingField)来存储参数值。这个字段是隐式生成的,没有显式代码定义。

  2. 单元测试项目的编译上下文差异

    • 若单元测试项目的C#语言版本未设置为12,编译器会以旧语法逻辑处理主构造函数,导致参数无法正确绑定到合成字段,最终在方法调用时出现空引用。
    • 部分情况下,Rider的代码优化或编译上下文同步问题,会导致单元测试项目生成的IL代码与主项目不一致,使得手动传入的依赖实例未被正确赋值到合成字段中。
  3. DI容器与手动构造的执行逻辑差异
    实际运行时通过DI容器注入时,容器会严格匹配主构造函数签名传递实例,且DI容器的初始化逻辑与主项目编译上下文一致,能正确识别并赋值合成字段;但单元测试中手动构造时,若编译上下文不一致,就会出现参数绑定失败的情况。

解决方案

  • 确保单元测试项目的C#语言版本设置为12:在项目属性的“生成”选项中,将“语言版本”选择为“C# 12”。
  • 显式定义私有字段存储主构造函数参数,避免隐式合成字段的兼容性问题:
    public class AppService(IAppRepository appRepository) : IAppService
    {
      private readonly IAppRepository _appRepository = appRepository;
    
      public App Get(int id)
      {
        return _appRepository.Get(id);
      }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 13:08:24