Я создал юнит-тест, и мне нужно смоделировать 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;
}