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

单元测试返回Null问题:FirstOrDefault谓词导致partProdArea为空

单元测试中AutoFixture+NSubstitute+Fluent Assertions遇到的FirstOrDefault空值问题

我在单元测试中使用AutoFixture、NSubstitute和Fluent Assertions。主代码的Update方法里,通过repository.GetQueryable().FirstOrDefault(predicate: q => q.Id == planInventoryDto.Id)获取的partProdArea为Null,导致测试返回错误结果。试了多种方法都没解决,想问下有没有其他编写带谓词的FirstOrDefault的方式?

主代码

public async Task<ServiceResponse<PlanInventoryDto>> Update(PlanInventoryDto planInventoryDto)
{
    planInventoryDto.ThrowIfNull(nameof(planInventoryDto));

    if (planInventoryDto == null)
    {
        return serviceResponseHelper.SetError<PlanInventoryDto>(null, localize["InvalidModel"], StatusCodes.Status400BadRequest, true);
    }

    var partProdArea = repository.GetQueryable().FirstOrDefault(predicate: q => q.Id == planInventoryDto.Id);

    if (partProdArea != null)
    {
        var mappingResult = mapper.Map(planInventoryDto, partProdArea);

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

        var result = repository.GetFirstOrDefault(predicate: p => p.Id == planInventoryDto.Id);

        var dto = mapper.Map<PlanInventory, PlanInventoryDto>(result);

        return serviceResponseHelper.SetSuccess(dto);
    }

    return serviceResponseHelper.SetError<PlanInventoryDto>(null, localize["ModelIsNotFound"], StatusCodes.Status400BadRequest, true);
}

单元测试代码

[Fact]
[Trait("Operation", "Update")]
[Trait("Category", "PlanInventory")]
public async Task PlanInventoryService_Update_ReturnsSuccessResponse()
{
    // Arrange
    var planInventoryDto = _fixture.Create<PlanInventoryDto>();
    var planInventory = _fixture.Build<Domain.PlanProcess.PlanInventory>()
        .With(p => p.Id, planInventoryDto.Id)
        .Create();
    var updatedPlanInventory = _fixture.Create<Domain.PlanProcess.PlanInventory>();
    var updatedDto = _fixture.Create<PlanInventoryDto>();

    _repository.GetFirstOrDefault(Arg.Any<Expression<Func<Domain.PlanProcess.PlanInventory, bool>>>(),
        Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IOrderedQueryable<Domain.PlanProcess.PlanInventory>>>(),
        Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IIncludableQueryable<Domain.PlanProcess.PlanInventory, object>>>())
        .Returns(planInventory); 

    _repository.GetFirstOrDefault(Arg.Any<Expression<Func<Domain.PlanProcess.PlanInventory, bool>>>(),
        Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IOrderedQueryable<Domain.PlanProcess.PlanInventory>>>(),
        Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IIncludableQueryable<Domain.PlanProcess.PlanInventory, object>>>())
        .Returns(updatedPlanInventory); 

    _mapper.Map(Arg.Any<PlanInventoryDto>(), Arg.Any<Domain.PlanProcess.PlanInventory>())
        .Returns(updatedPlanInventory);
    _repository.UpdateAsync(Arg.Any<Domain.PlanProcess.PlanInventory>())
        .Returns(Task.FromResult(updatedPlanInventory));
    _mapper.Map<Domain.PlanProcess.PlanInventory, PlanInventoryDto>(updatedPlanInventory)
        .Returns(updatedDto);

    // Act
    var result = await _planInventoryService.Update(planInventoryDto);

    // Assert
    _serviceResponseHelper.Received(1).SetSuccess(Arg.Any<PlanInventoryDto>());
}

解决方法

1. 修复单元测试的Mock逻辑

问题核心是你只Mock了GetFirstOrDefault方法,但主代码里调用的是GetQueryable().FirstOrDefault(...),这个调用链没有被Mock,导致GetQueryable()返回空的IQueryable,最终FirstOrDefault返回null。

需要给GetQueryable()添加Mock,让它返回包含目标实体的查询集合:

// 在Arrange部分添加这行代码
_repository.GetQueryable().Returns(new List<Domain.PlanProcess.PlanInventory> { planInventory }.AsQueryable());

2. 优化主代码的查询写法(可选)

如果你的仓储已经提供了带谓词的GetFirstOrDefault方法,直接用它替代链式调用,减少依赖:

// 替换原来的GetQueryable().FirstOrDefault(...)
var partProdArea = repository.GetFirstOrDefault(predicate: q => q.Id == planInventoryDto.Id);

或者简化FirstOrDefault的写法(和原逻辑等价,但更简洁):

var partProdArea = repository.GetQueryable().FirstOrDefault(q => q.Id == planInventoryDto.Id);

3. 修正重复Mock的问题

你两次Mock了GetFirstOrDefault,第二次会覆盖第一次的返回值,应该用序列返回的方式来模拟两次调用:

// 替换原来的两个GetFirstOrDefault Mock
_repository.GetFirstOrDefault(Arg.Any<Expression<Func<Domain.PlanProcess.PlanInventory, bool>>>(),
    Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IOrderedQueryable<Domain.PlanProcess.PlanInventory>>>(),
    Arg.Any<Func<IQueryable<Domain.PlanProcess.PlanInventory>, IIncludableQueryable<Domain.PlanProcess.PlanInventory, object>>>())
    .ReturnsNextFromSequence(planInventory, updatedPlanInventory);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 15:53:12