11

I'm creating some unit tests for my DAL that uses mongoDB c# driver. The thing is that I have this method that I want to test:

    public async virtual Task<IEnumerable<T>> GetAsync(Expression<Func<T, bool>> predicate)
    {
        return (await Collection.FindAsync(predicate)).ToList();
    }

and using Moq I have mocked the collection like this:

var mockMongoCollectionAdapter = new Mock<IMongoCollectionAdapter<Entity>>();

var expectedEntities = new List<Entity>
{
    mockEntity1.Object,
    mockEntity2.Object
};

mockMongoCollectionAdapter.Setup(x => x.FindAsync(It.IsAny<Expression<Func<Entity,bool>>>(), null, default(CancellationToken))).ReturnsAsync(expectedEntities as IAsyncCursor<Entity>);

but as expectedEntities as IAsyncCursor<Entity> is null the test is not working.

What is the best way to mock this method and handle the IAsyncCursor?

joacoleza
  • 775
  • 1
  • 9
  • 26

2 Answers2

18

Mock the IAsyncCursor<TDocument> interface so that it can be enumerated. There are not many methods on the interface any way

var mockCursor = new Mock<IAsyncCursor<Entity>>();
mockCursor.Setup(_ => _.Current).Returns(expectedEntities); //<-- Note the entities here
mockCursor
    .SetupSequence(_ => _.MoveNext(It.IsAny<CancellationToken>()))
    .Returns(true)
    .Returns(false);
mockCursor
    .SetupSequence(_ => _.MoveNextAsync(It.IsAny<CancellationToken>()))
    .Returns(Task.FromResult(true))
    .Returns(Task.FromResult(false));

mockMongoCollectionAdapter
    .Setup(x => x.FindAsync(
            It.IsAny<Expression<Func<Entity, bool>>>(),
            null,
            It.IsAny<CancellationToken>()
        ))
    .ReturnsAsync(mockCursor.Object); //<-- return the cursor here.

For reference on how the cursor is enumerated take a look at this answer.

How is an IAsyncCursor used for iteration with the mongodb c# driver?

After this you will be able to understand why the mock was setup with sequences for the move next methods.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • what if _exepctedEntities_ are dynamic (like a GetEntityByName call) -- like in that case SetupGet would Returns . ... a lambda expression producing the list, which is not allowed – roberto tomás Jun 27 '22 at 13:26
8

If it helps anyone else.... off of @Nkosi's mocking answer I implemented the following class

public class MockAsyncCursor<T> : IAsyncCursor<T>
{
    private readonly IEnumerable<T> _items;
    private bool called = false;

    public MockAsyncCursor(IEnumerable<T> items)
    {
        _items = items ?? Enumerable.Empty<T>();
    }

    public IEnumerable<T> Current => _items;

    public bool MoveNext(CancellationToken cancellationToken = new CancellationToken())
    {
        return !called && (called = true);
    }

    public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(MoveNext(cancellationToken));
    }

    public void Dispose()
    {
    }
}


Nkosi
  • 235,767
  • 35
  • 427
  • 472
Steve
  • 976
  • 12
  • 12