【问题标题】:Mocking async query operations模拟异步查询操作
【发布时间】:2019-07-20 20:05:33
【问题描述】:

我创建了单元测试,我必须模拟我的 EF 的 context。我使用Moq 库和Xunit。我有一个这样的测试方法:

    [Fact]
    public async Task DeleteAttachmentCommandHandler_WithValidCommand_ShouldCallSaveChangesAsyncOnce()
    {
        var command = new DeleteAttachmentCommand { Id = Guid.NewGuid() };
        var attachments = new List<Attachment>();

        var dbSetMock = attachments.AsQueryable().BuildMockDbSetForAsyncQueryOperations();
        _context.Setup(x => x.Set<Attachment>()).Returns(dbSetMock.Object);
        _context.Setup(x => x.SaveChangesAsync(It.IsAny<CancellationToken>())).ReturnsAsync(1).Verifiable();

        await Act(command);

        _context.Verify(x => x.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
    }

_context 的类型为 Mock&lt;IEmployeeSettlementsDbContext&gt;

BuildMockDbSetForAsyncQueryOperations 是我的扩展方法,这要归功于 MSDN 文档 -> https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking,它使用像 TestDbAsyncEnumerable、TestDbAsyncEnumerator、TestDbAsyncQueryProvider 这样的异步提供程序。我的扩展BuildMockDbSetForAsyncQueryOperations 看起来像这样:

public static Mock<IDbSet<TEntity>> BuildMockDbSetForAsyncQueryOperations<TEntity>(this IQueryable<TEntity> data) where TEntity : class
    {
        var dbSetMock = new Mock<IDbSet<TEntity>>();

        dbSetMock.As<IDbAsyncEnumerable<TEntity>>().Setup(x => x.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator()));
        dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(data.Provider));
        dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.Expression).Returns(data.Expression);
        dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.ElementType).Returns(data.ElementType);
        dbSetMock.As<IQueryable<TEntity>>().Setup(x => x.GetEnumerator()).Returns(data.GetEnumerator());

        return dbSetMock;
    }

在我正在测试的实际处理程序方法中,我有一行:

var attachment = await _context.Set<Attachment>()
    .SingleAsync(x => x.Id == command.Id);

当我运行测试时它失败并显示消息

消息:System.InvalidOperationException:序列不包含匹配元素。

但是当我将处理程序中的查询更改为 ListAsync() 而不是 SingleAsync() 时,模拟设置正确并返回一个空列表。但它不适用于使用 SingleAsync() 的单个元素。

编辑: 这是我完整的 Handler 方法:

    public async Task<Unit> Handle(DeleteAttachmentCommand command, CancellationToken cancellationToke = default(CancellationToken))
    {
        ValidateCommand(command);

        var attachment = await _context.Set<Attachment>().SingleAsync(x => x.Id == command.Id);
        attachment.IsDeleted = true;

        await _context.SaveChangesAsync();

        return Unit.Value;
    }

【问题讨论】:

  • 为什么你的var attachments = new List&lt;Attachment&gt;(); 是空的。请添加一些虚拟数据,然后再次检查。
  • @TanvirArjel,我做到了。最简单的形式:var attachments = new List&lt;Attachment&gt;{ new Attachment() }; - 行为完全相同
  • 不!你又错了。让它var attachments = new List&lt;Attachment&gt;{ new Attachment(){Id = 1, .....} };然后_context.Setup(x =&gt; x.Set&lt;Attachment&gt;()).Returns(dbSetMock.Object.Where(x =&gt; x.Id == 1));。现在你的问题应该解决了。让我知道更新,我会将其发布为答案。
  • 哦,这样,谢谢,我试试,但我得到一个转换错误:cannot convert from 'System.Linq.IQueryable&lt;Attachment&gt;' to 'System.Data.Entity.IDbSet&lt;Attachment&gt;' ?
  • 是的!试一试,让我知道。

标签: c# entity-framework unit-testing asynchronous mocking


【解决方案1】:

SingleAsync()MSDN 中的文档

异步返回一个序列的唯一元素,并抛出一个 如果序列中不完全是一个元素,则异常。

考虑使用FirstOrDefault()或FirstOrDefaultAsync()方法代替SingleAsync()。 Here 和 here 是一个链接,这不会抛出异常。

【讨论】:

  • 如果我将其更改为 FirstOrDefaultAsync,它会返回我 null 而不是空的 Attachment 元素
  • 我知道First 和Single 之间的区别,这不是这个问题的重点。模拟 EF 异步操作对我来说没有正确模拟,而仅适用于 Single 和 First(单个元素)。对于列表,它可以正常工作
猜你喜欢
  • 1970-01-01
  • 2020-01-26
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
相关资源
最近更新 更多