使用NUnit和MOQ做单元测试:如何Mock EF Core的FirstOrDefaultAsync查询?
使用Moq配置EF Core查询的单元测试方案
核心思路
要让这段EF Core异步查询在单元测试中通过Moq正常工作,需要模拟DbContext及其Notes集合(DbSet<Note>),同时让模拟的集合支持EF Core的异步LINQ方法(比如FirstOrDefaultAsync)。
具体实现步骤
1. 准备测试数据
先创建符合查询条件的Note实体列表,并转换为可查询对象:
var testNotes = new List<Note> { new Note { IntNoteId = 1, Content = "测试笔记1" }, new Note { IntNoteId = 2, Content = "测试笔记2" } }; var queryableNotes = testNotes.AsQueryable();
2. 模拟支持异步的DbSet
由于FirstOrDefaultAsync是异步扩展方法,Moq无法直接配置,需要让模拟的DbSet实现异步查询相关接口:
var mockNotesDbSet = new Mock<DbSet<Note>>(); // 配置IQueryable核心属性 mockNotesDbSet.As<IQueryable<Note>>() .Setup(m => m.Provider) .Returns(new TestAsyncQueryProvider<Note>(queryableNotes.Provider)); mockNotesDbSet.As<IQueryable<Note>>() .Setup(m => m.Expression) .Returns(queryableNotes.Expression); mockNotesDbSet.As<IQueryable<Note>>() .Setup(m => m.ElementType) .Returns(queryableNotes.ElementType); mockNotesDbSet.As<IQueryable<Note>>() .Setup(m => m.GetEnumerator()) .Returns(queryableNotes.GetEnumerator()); // 配置异步枚举器,支持异步方法调用 mockNotesDbSet.As<IAsyncEnumerable<Note>>() .Setup(m => m.GetAsyncEnumerator(It.IsAny<CancellationToken>())) .Returns(new TestAsyncEnumerator<Note>(queryableNotes.GetEnumerator()));
需要以下3个辅助类实现异步查询支持:
// 异步枚举器实现 public class TestAsyncEnumerator<T> : IAsyncEnumerator<T> { private readonly IEnumerator<T> _innerEnumerator; public TestAsyncEnumerator(IEnumerator<T> innerEnumerator) { _innerEnumerator = innerEnumerator; } public ValueTask DisposeAsync() { _innerEnumerator.Dispose(); return ValueTask.CompletedTask; } public ValueTask<bool> MoveNextAsync() { return ValueTask.FromResult(_innerEnumerator.MoveNext()); } public T Current => _innerEnumerator.Current; } // 异步查询提供程序实现 public class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider { private readonly IQueryProvider _innerProvider; public TestAsyncQueryProvider(IQueryProvider innerProvider) { _innerProvider = innerProvider; } public IQueryable CreateQuery(Expression expression) => new TestAsyncEnumerable<TEntity>(expression); public IQueryable<TElement> CreateQuery<TElement>(Expression expression) => new TestAsyncEnumerable<TElement>(expression); public object Execute(Expression expression) => _innerProvider.Execute(expression); public TResult Execute<TResult>(Expression expression) => _innerProvider.Execute<TResult>(expression); public IAsyncEnumerable<TResult> ExecuteAsync<TResult>(Expression expression) => new TestAsyncEnumerable<TResult>(expression); public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken) => Task.FromResult(Execute<TResult>(expression)); } // 异步可枚举集合实现 public class TestAsyncEnumerable<T> : EnumerableQuery<T>, IAsyncEnumerable<T>, IQueryable<T> { public TestAsyncEnumerable(IEnumerable<T> enumerable) : base(enumerable) { } public TestAsyncEnumerable(Expression expression) : base(expression) { } public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new TestAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); } IQueryProvider IQueryable.Provider => new TestAsyncQueryProvider<T>(this); }
3. 关联DbContext与模拟DbSet
配置DbContext的Notes属性,返回上述模拟的DbSet:
var mockDbContext = new Mock<YourDbContext>(); mockDbContext.Setup(m => m.Notes) .Returns(mockNotesDbSet.Object);
4. 执行测试验证
在测试方法中注入模拟的DbContext,执行目标代码并验证结果:
[Test] public async Task GetNote_ByIntNoteId_ReturnsCorrectNote() { // 初始化模拟对象 var referenceId = 1; var mockDbContext = SetupMockDbContextWithNotes(); // 封装上述模拟逻辑的方法 // 执行目标查询 var targetService = new YourService(mockDbContext.Object); var result = await targetService.GetNoteAsync(referenceId); // 验证结果 Assert.NotNull(result); Assert.AreEqual(referenceId, result.IntNoteId); }
简化替代方案:使用EF Core内存数据库
如果不想编写辅助类,可直接用EF Core内存数据库,它天然支持异步查询,配置更简洁:
[Test] public async Task GetNote_ByIntNoteId_ReturnsCorrectNote() { // 创建内存数据库配置 var options = new DbContextOptionsBuilder<YourDbContext>() .UseInMemoryDatabase(databaseName: "Test_Notes_Db") .Options; // 初始化测试数据 using var context = new YourDbContext(options); context.Notes.AddRange(new List<Note> { new Note { IntNoteId = 1, Content = "测试笔记1" }, new Note { IntNoteId = 2, Content = "测试笔记2" } }); await context.SaveChangesAsync(); // 执行查询并验证 var targetService = new YourService(context); var result = await targetService.GetNoteAsync(1); Assert.NotNull(result); Assert.AreEqual(1, result.IntNoteId); }
内容的提问来源于stack exchange,提问作者stefan
相关产品推荐
相关产品推荐

