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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 09:20:55