Поддельные операции асинхронного запроса - PullRequest
0 голосов
/ 27 февраля 2019

Я создал юнит-тест, и мне нужно смоделировать 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,, который использует асинхронные провайдеры, такие как 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().

РЕДАКТИРОВАТЬ: Вот мой полный метод обработчика:

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

1 Ответ

0 голосов
/ 27 февраля 2019

SingleAsync() документация в MSDN

Асинхронно возвращает единственный элемент последовательности и выдает исключение, если в последовательности нет точно одного элемента.

Попробуйте использовать FirstOrDefault() или FirstOrDefaultAsync() метод вместо SingleAsync(). Здесь и здесь - это ссылка о том, что исключение не возникнет.

...