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

使用AutoFixture和NSubstitute编写MVC单元测试时遇空引用错误

MVC项目中AutoFixture+NSubstitute单元测试Update方法空引用问题

在MVC项目中使用AutoFixture和NSubstitute编写单元测试时,Update和Delete方法触发“对象引用未设置到对象实例”错误,错误发生在业务代码的var parameter = await repository.GetFirstOrDefaultAsync(predicate: q => q.Id == parameterDto.Id);行,推测可能是包配置或测试依赖模拟存在遗漏。

业务代码

public async Task<ServiceResponse<ParameterDto>> Update(ParameterDto parameterDto)
{
    try
    {
        if (parameterDto == null)
        {
            return serviceResponseHelper.SetError<ParameterDto>(null, localize["InvalidModel"], StatusCodes.Status400BadRequest, true);
        }

        var parameter = await repository.GetFirstOrDefaultAsync(predicate: q => q.Id == parameterDto.Id);

        if (parameter != null)
        {
            parameter.CdParam = parameterDto.CdParam;
            parameter.CdPlant = parameterDto.CdPlant;
            parameter.ChDescr = parameterDto.ChDescr;
            parameter.ChParam = parameterDto.ChParam;
            parameter.NoOrder = parameterDto.NoOrder;
            parameter.SwActive = parameterDto.SwActive;
            parameter.SwAllowDel = parameterDto.SwAllowDel;
            parameter.SwAllowNew = parameterDto.SwAllowNew;
            parameter.SwAllowUpd = parameterDto.SwAllowUpd;
            parameter.SwParent = parameterDto.SwParent;

            await repository.UpdateAsync(parameter).ConfigureAwait(false);

            var updatedParameter = await repository.GetFirstOrDefaultAsync(predicate: p => p.Id == parameterDto.Id);
            var dto = mapper.Map<Domain.Definition.Parameter, ParameterDto>(updatedParameter);

            return serviceResponseHelper.SetSuccess(dto);
        }

        return serviceResponseHelper.SetError<ParameterDto>(null, localize["ModelIsNotFound"], StatusCodes.Status400BadRequest, true);
    }
    catch (Exception ex)
    {
        return serviceResponseHelper.SetError<ParameterDto>(null, ex.InnerException?.Message ?? ex.Message, StatusCodes.Status500InternalServerError, true);
    }
}

单元测试代码

[Fact]
public async Task ParameterService_Update_ReturnsUpdatedParameterDto()
{
    // Arrange
    var fixture = new Fixture();
    var parameterDto = fixture.Create<ParameterDto>();
    var parameter = fixture.Build<Domain.Definition.Parameter>()
        .With(p => p.Id, parameterDto.Id)  // Set the same Id as the parameterDto for matching
        .Create();
    var updatedParameter = fixture.Create<Domain.Definition.Parameter>();
    var expectedResponse = new ServiceResponse<ParameterDto>(fixture.Create<ParameterDto>());

    _repository.GetFirstOrDefaultAsync(Arg.Any<Expression<Func<Domain.Definition.Parameter, bool>>>(), null, null, default)
        .Returns(parameter);
    _repository.UpdateAsync(Arg.Any<Domain.Definition.Parameter>())
        .Returns(Task.FromResult(updatedParameter));
    _repository.GetFirstOrDefaultAsync(Arg.Any<Expression<Func<Domain.Definition.Parameter, bool>>>(), null, null, default)
        .Returns(updatedParameter);
    _mapper.Map<Domain.Definition.Parameter, ParameterDto>(Arg.Any<Domain.Definition.Parameter>())
        .Returns(expectedResponse.Result);

    // Act
    var result = await _parameterService.Update(parameterDto);

    // Assert
    result.Should().BeEquivalentTo(expectedResponse);
    result.Result.Should().BeEquivalentTo(expectedResponse.Result);
    result.Should().NotBeNull();
    result.IsSuccessful.Should().BeTrue();
}

问题分析与解决方案

核心问题1:Repository方法返回值被覆盖

NSubstitute中多次对同一个方法设置Returns时,最后一次配置会覆盖之前的。测试代码中先设置GetFirstOrDefaultAsync返回原parameter,随后又设置它返回updatedParameter,导致业务代码第一次调用该方法时,实际返回的是updatedParameter(而非预期的原parameter),若updatedParameter的Id与parameterDto.Id不匹配,会导致后续逻辑异常;更关键的是,如果_repository本身未被正确注入到_parameterService中,会直接触发空引用。

核心问题2:依赖未正确注入

确保_parameterService依赖的repository、serviceResponseHelper、localize、mapper均已通过NSubstitute创建模拟实例,并正确注入到服务类中,避免任何依赖为null。

修正步骤

  1. 调整Repository返回值配置
    将两次GetFirstOrDefaultAsync的返回值设置合并为一次,用逗号分隔多个返回值,实现第一次调用返回原对象、第二次调用返回更新后对象:

    _repository.GetFirstOrDefaultAsync(Arg.Any<Expression<Func<Domain.Definition.Parameter, bool>>>(), null, null, default)
        .Returns(parameter, updatedParameter);
    
  2. 验证依赖注入完整性
    在测试类的初始化逻辑(如构造函数或[SetUp]方法)中,确保所有依赖都已初始化:

    private IParameterRepository _repository;
    private IMapper _mapper;
    private IServiceResponseHelper _serviceResponseHelper;
    private IStringLocalizer<ParameterService> _localize;
    private ParameterService _parameterService;
    
    [SetUp]
    public void Setup()
    {
        _repository = Substitute.For<IParameterRepository>();
        _mapper = Substitute.For<IMapper>();
        _serviceResponseHelper = Substitute.For<IServiceResponseHelper>();
        _localize = Substitute.For<IStringLocalizer<ParameterService>>();
        _parameterService = new ParameterService(_repository, _mapper, _serviceResponseHelper, _localize);
    }
    
  3. 显式验证参数ID有效性
    可添加断言确保parameterDto.Id不为默认值,避免查询逻辑无意义:

    parameterDto.Id.Should().NotBe(default);
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:43:12