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

如何在XUnit测试中正确使用FirstOrDefaultAsync方法?

解决XUnit测试中FirstOrDefaultAsync的异步等待问题

问题核心是没有正确Mock业务代码中用到的异步查询依赖,同时要确保异步测试的规范执行,具体修复步骤如下:

1. 确认异步测试的基础规范

你的测试方法已经使用async Task定义,这符合XUnit的异步测试要求,XUnit会自动等待异步任务完成,这部分无需调整。

2. 补全UserExam查询的Mock逻辑

业务代码中调用了_persistenceContext.Query<UserExam>(),但测试只Mock了Query<Test>(),导致执行到UserExam的异步查询时没有预期数据源,异步流程异常。需要添加对应的Mock:

// 在测试的Arrange部分添加
// 根据测试场景构造预期数据,示例为返回空结果(无匹配UserExam)
_context.Setup(c => c.Query<UserExam>()).Returns(new List<UserExam>().AsQueryable());

如果测试需要匹配的UserExam数据,可构造对应对象:

_context.Setup(c => c.Query<UserExam>()).Returns(new List<UserExam>
{
    new UserExam
    {
        UserId = 1, // 和后续Mock的UserSessionService返回值一致
        ExamId = request.ExamId,
        Type = (TrackingExamTypeEnum)exTypeId // 匹配业务代码的类型转换值
    }
}.AsQueryable());

3. 补全UserSessionService的Mock(关键遗漏点)

业务代码中_userId = _userSessionService.GetUserId();,如果未Mock该服务,_userId会是默认值(如0),导致查询条件不匹配,异步结果不符合预期。需要Mock这个依赖:

// 在测试类中添加Mock对象
private readonly Mock<IUserSessionService> _userSessionServiceMock = new Mock<IUserSessionService>();

// 在测试构造函数中注入并配置
public Test()
{
    // 模拟返回测试用的用户ID
    _userSessionServiceMock.Setup(s => s.GetUserId()).Returns(1);
    
    _examService = new ExamService(
        _context.Object,
        _userSessionServiceMock.Object // 注入Mock服务
    );
}

4. 验证异步方法的执行逻辑

FirstOrDefaultAsync是EF Core的静态扩展方法,Moq无法直接Mock。通过MockQuery<UserExam>()返回包含预期数据的IQueryable,让异步扩展方法在该数据源上执行,就能正确返回异步结果,测试会自动等待任务完成。

修复后的完整测试代码示例

public class Test
{
    private readonly ExamService _examService;
    private readonly Mock<Context> _context = new Mock<Context>();
    private readonly Mock<IUserSessionService> _userSessionServiceMock = new Mock<IUserSessionService>();

    public Test()
    {
        // Mock用户ID返回
        _userSessionServiceMock.Setup(s => s.GetUserId()).Returns(1);
        
        _examService = new ExamService(
            _context.Object,
            _userSessionServiceMock.Object
        );
    }

    [Fact]
    public async Task GetTrialExamDetail_WithoutTestSections_ReturnsCorrectResponse()
    {
        // Arrange
        var request = new GetDetailRequest
        {
            ExamId = 1,
            STypeId = Enums.SessionType.A
        };

        // Mock Test查询
        _context.Setup(c => c.Query<Test>()).Returns(new List<Test>()
        {
            new Test
            {
                ExamId = 1,
                Url = "test"
            }
        }.AsQueryable());

        // Mock缓存查询
        _context.Setup(p => p.GetListFromCache<ExamTest>(
            It.IsAny<string>(),
            null))
        .Returns((string cacheKey, TimeSpan? slidingExpiration) =>
        {
            return new List<ExamTest>();
        });

        // Mock UserExam查询
        _context.Setup(c => c.Query<UserExam>()).Returns(new List<UserExam>().AsQueryable());

        // Act
        var result = await _examService.GetDetail(request);

        // Assert
        Assert.Equal(Enums.ResponseStatus.Success, result.ResponseStatus);
        Assert.Equal(Enums.ResponseStatus.Success.ToString(), result.ResponseMessage);
        Assert.Empty(result.ResponseData.Exams);
    }
}

关键注意事项

  • 业务代码中所有依赖(如UserSessionService、各类Query调用)都要在测试中Mock,保证测试环境独立。
  • 异步测试必须用async Task作为返回类型,禁止使用Wait()或Result,避免死锁风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:35:11