MongoDB Driver Mocking and Unit Testing method that returns IAsyncCursor with example

MongoDB Mock and Unit Test IAsyncCursor

In this article, I shall talk about how to perform MongoDB Mock and Unit Test IAsyncCursor using asynchronous behaviors.

We will see in detail how to Unit test and Mock IAsyncCursor so that methods using this interface can be easily unit tested.

If interested in the Synchronous method using IAsyncursor, please see the below article for example,

Please note that sync and async methods are having slight differences for to setup mock on the Interface.

MongoDB driver exposes multiple extension methods, that let you perform almost most of the basic CRUD operations. These basic extension methods often use IAsyncCursor which helps in iterating or enumerating elements in over documents for a given any given filter criteria.

Technically IAsyncCursor is equivalent to IEnumerable which provides the capability of iterating over the collection in an asynchronous way.

Below examples are already discussed in detail in post-MongoDB Repository implementation in .NET Core with example

Example

Below is an example method for which we shall be writing mocking and unit test cases.

 
public async Task<TEntity> Get(string id)
        {
            //ex. 5dc1039a1521eaa36835e541

            var objectId = new ObjectId(id);

            FilterDefinition<TEntity> filter = Builders<TEntity>.Filter.Eq("_id", objectId);

            _dbCollection = _mongoContext.GetCollection<TEntity>(typeof(TEntity).Name);

            return await _dbCollection.FindAsync(filter).Result.FirstOrDefaultAsync();

        }

Let’s define the constructor for initial setup up (If using XUnit this constructor shall execute for every test case and set up initial test data required for each test case)

Constructor for the Test class – Mock the MongoDB Collection

We shall create mock for MongoDB Driver interfaces like IMongoCollection, IMongoDBContext as below,

Please note the _list object which will be used to iterate over the collection.

public BooksRepositoryTests()
        {
            string id = "5dc1039a1521eaa36835e541";
            var objectId = new ObjectId(id);

            _book = new Book
            {
                Author = "thecodebuzz",
                Name = "test",
                Category = "test",
                Price = 1231,
                Id = objectId
            };
            _mockCollection = new Mock<IMongoCollection<Book>>();
            _mockCollection.Object.InsertOne(_book);
            _mockContext = new Mock<IMongoBookDBContext>();
            _list = new System.Collections.Generic.List<Book>();
            _list.Add(_book);
       
        }

Unit test and Mock IAsyncCursor

Let’s now mock IAsyncursor. Overall there are two methods that IAsyncursor exposes. We shall mock them easily.

The IAsyncCursor<TDocument> type exposes the following methods,

  • MoveNext()
  • MoveNextAsync()

We shall set up current as a list of books as defined in the mock above.

//Mock MoveNextAsync
            
Mock<IAsyncCursor<Book>> _bookCursor = new Mock<IAsyncCursor<Book>>();
            _bookCursor.Setup(_ => _.Current).Returns(_list); 
            _bookCursor
                .SetupSequence(_ => _.MoveNext(It.IsAny<CancellationToken>()))
                .Returns(true)
                .Returns(false);
            _bookCursor
                .SetupSequence(_ => _.MoveNextAsync(It.IsAny<CancellationToken>()))
                .Returns(Task.FromResult(true))
                .Returns(Task.FromResult(false));

Define Test Methods as below,

        [Fact]
        public async void BookRepository_GetBook_Valid_Success()
        {
            //Arrange

            //Mock FindAsync
            _mockCollection.Setup(op => op.FindAsync(It.IsAny<FilterDefinition<Book>>(),
            It.IsAny<FindOptions<Book, Book>>(),
             It.IsAny<CancellationToken>())).ReturnsAsync(_bookCursor.Object);

            //Mock GetCollection
            _mockContext.Setup(c => c.GetCollection<Book>(typeof(Book).Name)).Returns(_mockCollection.Object);

            var bookRepo = new BookRepository(_mockContext.Object);

            //Act
            var result = await bookRepo.Get("5dc1039a1521eaa36835e541");

            //Assert 

            Assert.NotNull(result);
            Assert.Equal(result.Name,_book.Name);
            Assert.Equal(result.Author, _book.Author);

            //Verify if InsertOneAsync is called once 
            _mockCollection.Verify(c => c.FindAsync(It.IsAny<FilterDefinition<Book>>(),
                It.IsAny<FindOptions<Book>>(),
                It.IsAny<CancellationToken>()), Times.Once);
        }

With the above code and mock setup, I can very much unit test actual result vs expected result easily. Apart from that, I can very much Assert using Verify() to confirm if the extension method has been invoked once.

Mock and Unit Test a method returning IAsyncCursor

Other references :

Do you have any better approach for Unit testing IAsyncCursor?

Do you have any comments or ideas or any better suggestions to share?

Please sound off your comments below.

Happy Coding !!



Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.



6 thoughts on “MongoDB Mock and Unit Test IAsyncCursor

  1. Do you have a complete example of this code? I am having trouble getting this to work and it would be helpful to see what IMongoBookDBContext and BookRepository actually look like.

      1. Thanks — I was actually able to get it working without the example code. I had to actually add entries to my version of _list (which was missing from the code in your article). Either way, the example code link will hopefully help others in the future! Thanks again.

      2. Hi – I am glad the issue got resolved now. I updated the post with a code example. I appreciate your comments. Hopefully i will see your feedback on my other post. Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *