首页 > 解决方案 > 模拟异步查询操作

问题描述

我创建了单元测试,我必须模拟context我的 EF。我使用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<IEmployeeSettlementsDbContext>

BuildMockDbSetForAsyncQueryOperations是我的扩展方法,这要归功于 MSDN 文档 -> https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking,它使用了异步提供程序,如TestDbAsyncEnumerableTestDbAsyncEnumeratorTestDbAsyncQueryProvider。我的扩展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().

编辑:这是我的完整处理程序方法:

    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;
    }

标签: c#entity-frameworkunit-testingasynchronousmocking

解决方案


SingleAsync()MSDN中的文档

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

考虑使用FirstOrDefault()orFirstOrDefaultAsync()方法代替SingleAsync(). 这里这里是一个关于那个的链接,这不会抛出异常。


推荐阅读