How To Mock MongoDB SDK Cursor/Methods For Unit Testing DAL In C#

MongoDB is such a popular database, we frequently find ourselves having to fake MongoDB SDK methods in order to unit test DAL operations. When you wish to simulate the cursors and create unit test cases, it can be a little challenging.

 

Here are a few examples of mock unit tests written in C# using XUnit and Moq using mock MongoDB SDK.

Class to be Unit tested

Consider the unit testing of the DAL layer class below. IMongoClient, a singleton defined at.net core startup, is introduced into the constructor. The constructor of IMongoClient starts database and collection instances.

GetProducts is a method that uses an aggregating pipeline to return a list of items that satisfy the provided criteria (utilizing MongoDB Atlas search). Ignore this convoluted pipeline. I used it to illustrate the significance of unit testing DAL techniques.

public class MongoDBService {
    private readonly IMongoCollection < Products > productCollection;
    public MongoDBService(IMongoClient mongoClient) {
        var mongoDB = mongoClient.GetDatabase("databaseName");
        this.productCollection = mongoDB.GetCollection < Products > ("collectionName");
    }
    public async Task < List < Products >> GetProducts(SearchRequestDTO searchRequestDTO) {
        //Aggregation pipeline stages construction starts.
        var departmentfilter = new BsonDocument {
            {
                MongoDBConstants.QueryOperator, searchRequestDTO.Department
            }, {
                MongoDBConstants.PathOperator,
                ProductInfoConstants.Department
            }
        };
        var categories = new BsonArray();
        categories.AddRange(searchRequestDTO.Categories);
        var categoryFilter = new BsonDocument {
            {
                MongoDBConstants.QueryOperator, categories
            }, {
                MongoDBConstants.PathOperator,
                ProductInfoConstants.Categories
            }
        };
        var must = new BsonArray();
        must.Add(new BsonDocument {
            {
                MongoDBConstants.PhraseOperator, departmentfilter
            }
        });
        must.Add(new BsonDocument {
            {
                MongoDBConstants.PhraseOperator, categoryFilter
            }
        });
        foreach(var attribute in searchRequestDTO.AttributeFilter) {
            string path = string.Empty;
            ProductInfoConstants.Attributes.TryGetValue(attribute.K, out path);
            var attributefilter = new BsonDocument {
                {
                    MongoDBConstants.QueryOperator, attribute.V
                }, {
                    MongoDBConstants.PathOperator,
                    path
                }
            };
            must.Add(new BsonDocument {
                {
                    MongoDBConstants.PhraseOperator, attributefilter
                }
            });
        }
        var compound = new BsonDocument {
            {
                MongoDBConstants.MustOperator, must
            }
        };
        var search = new BsonDocument {
            {
                MongoDBConstants.IndexKey, MongoDBConstants.DefaultIndexvalue
            }, {
                MongoDBConstants.CompoundOperator,
                compound
            }
        };
        var searchStage = new BsonDocument {
            {
                MongoDBConstants.SearchOperator, search
            }
        };
        var pipeline = new [] {
            searchStage
        };
        //aggregation pipeline stages construction ends.
        var result = await this.productCollection.AggregateAsync < Products > (pipeline);
        return await result.ToListAsync();
    }
}

 

A database call to MongoDB is made in the procedure above; this call needs to be mocked.

await this.productCollection.AggregateAsync<Products>(pipeline)

 

Unit Test Step

Let’s follow the methods below to accomplish the same goal and create a unit test for the GetProcuts Method using C3, XUnit, and Moq.

 

Step 1

IMongoClient, IMongoDatabase, IMongoCollection for Products, IAsyncCursor for Products, and product collection sample data must all be mocked. I’ve only injected one substance here. You may include as many test results as you like.

Note: Products is the data model of the collection.

public class MongoDBServiceUnitTest {
    private Mock < IMongoClient > mongoClient;
    private Mock < IMongoDatabase > mongodb;
    private Mock < IMongoCollection < Products >> productCollection;
    private List < Products > productList;
    private Mock < IAsyncCursor < Products >> productCursor;
    public MongoDBServiceUnitTest() {
        this.settings = new MongoDBSettings("ecommerce-db");
        this.mongoClient = new Mock < IMongoClient > ();
        this.productCollection = new Mock < IMongoCollection < Products >> ();
        this.mongodb = new Mock < IMongoDatabase > ();
        this.productCursor = new Mock < IAsyncCursor < Products >> ();
        var product = new Products {
            Id = "1",
                Attributes = new Dictionary < string, string > () {
                    {
                        "ram",
                        "4gb"
                    }, {
                        "diskSpace",
                        "128gb"
                    }
                },
                Categories = new List < string > () {
                    "Mobiles"
                },
                Department = "Electronics",
                Description = "Brand new affordable samsung mobile",
                Item = "Samsung Galaxy M31s",
                Sku = "sku1234567",
                Quantity = 99,
                Image = "url",
                InsertedDate = DateTime.UtcNow,
                UpdatedDate = DateTime.UtcNow,
                SchemaVersion = 1,
                ManufactureDetails = new ManufactureDetails() {
                    Brand = "Samsung",
                        Model = "M31s"
                },
                Pricing = new Pricing() {
                    Price = 15000,
                        Currency = "INR",
                        Discount = 1000,
                        DiscountExpireAt = DateTime.UtcNow.AddDays(10)
                },
                Rating = new Rating() {
                    AggregateRating = 4.3,
                        TotalReviews = 10000,
                        Stars = new List < int > () {
                            1,
                            2,
                            3,
                            4,
                            5
                        }
                }
        };
        this.productList = new List < Products > () {
            product
        };
    }
}

 

Step 2

When getcollection and getdatabase are called, add the private method below to configure IMongoDatabase and IMongoClient.

Before setting up an IMongoClient that returns an IMongoDatabase object, configure IMongoDatabase first.

private void InitializeMongoDb() {
    this.mongodb.Setup(x => x.GetCollection < Products > (MongoDBConstants.ProductCollectionName,
        default)).Returns(this.productCollection.Object);
    this.mongoClient.Setup(x => x.GetDatabase(It.IsAny < string > (),
        default)).Returns(this.mongodb.Object);
}

 

Step 3

To configure the cursor that returns productList, add the private method below. Using the product cursor, mock the Aggregationpipeline result after that.

private void InitializeMongoProductCollection() {
    this.productCursor.Setup( => .Current).Returns(this.productList);
    this.productCursor.SetupSequence( => .MoveNext(It.IsAny < CancellationToken > ())).Returns(true).Returns(false);
    this.productCursor.SetupSequence( => .MoveNextAsync(It.IsAny < CancellationToken > ())).Returns(Task.FromResult(true)).Returns(Task.FromResult(false));
    this.productCollection.Setup(x => x.AggregateAsync(It.IsAny < PipelineDefinition < Products, Products >> (), It.IsAny < AggregateOptions > (), It.IsAny < CancellationToken > ())).ReturnsAsync(this.productCursor.Object);
    this.InitializeMongoDb();
}

 

Step 4

The setup is now complete. For GetProducts, a unit case can be written.

[Fact]
public async Task GetProductsValidataData() {
    this.InitializeMongoProductCollection();
    var mongoDBService = new MongoDBService(this.settings, this.mongoClient.Object);
    var response = await mongoDBService.GetProducts(new Models.DTO.Request.SearchRequestDTO() {
        Department = "any",
            Categories = new List < string > () {
                "any"
            },
            AttributeFilter = new List < Models.DTO.Request.AttributeFilterDTO > () {
                new Models.DTO.Request.AttributeFilterDTO() {
                    K = "size",
                        V = "3"
                }
            }
    });
    // do assertion of the result from GetProducts.
}

 

We may simulate any other method in the MongoDB module for our unit testing, just as the AggregateAsync method.

 

Submit a Comment

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

Subscribe

Select Categories