简体   繁体   中英

Mocking async query operations

I created unit test and I have to mock context of my EF. I use Moq library and Xunit . I have a test method like this:

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

The _context is of type Mock<IEmployeeSettlementsDbContext>

The BuildMockDbSetForAsyncQueryOperations is my extension method thanks to the MSDN documentation -> https://docs.microsoft.com/en-us/ef/ef6/fundamentals/testing/mocking , which uses the async providers like TestDbAsyncEnumerable , TestDbAsyncEnumerator , TestDbAsyncQueryProvider . And my extension BuildMockDbSetForAsyncQueryOperations looks like this:

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

In my actual handler method which is testing I have a line:

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

And when I run the test it fails and show me the message

Message: System.InvalidOperationException : The sequence does not contain a matching element.

But when I change the query in handler to be ListAsync() instead of SingleAsync() then the mock is setup correctly and returns me an empty list. But it does not work for the single element using SingleAsync() .

EDIT: Here is my full Handler method:

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

SingleAsync() documentation in MSDN

Asynchronously returns the only element of a sequence, and throws an exception if there is not exactly one element in the sequence.

Consider to use FirstOrDefault() or FirstOrDefaultAsync() method instead of SingleAsync() . Here and here are a link about that, this will not throw exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM