如何在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
相关产品推荐
相关产品推荐

