amarsha4
amarsha4

Reputation: 481

How can I unit test a MongoDB query filter?

I have a scenario similar to the following (simplified for clarity):

public class ExampleRepository
{
    private readonly IMongoDatabase _db;

    public ExampleRepository(IMongoClient mongoClient)
    {
        this._db = mongoClient.GetDatabase("database");
    }

    public async Task<IEnumerable<Item>> GetItemsUsingFilter(Guid ownerId, DateTimeOffset createdSince, IEnumerable<ItemType> typesToIgnore)
    {
        var cursor = await this._db.GetCollection<Item>("Items")
            .FindAsync(item =>
                item.OwnerId == ownerId
                &&
                item.CreatedDate >= createdSince
                &&
                !typesToIgnore.Contains(item.Type)
            .SortByDescending(item => item.UserRating)
            .Limit(10);

        return await cursor.ToListAsync();
    }
}

I want to write unit tests to verify the correctness of my query filter (as well as the sort and limit calls), but I can't figure out how to set up the test data to do so.

I've tried mocking the IMongoDatabase and setting up the GetCollection call to return a mock IMongoCollection, but this isn't the right approach, since I need to invoke the FindAsync call on a real MongoCollection.

I looked into changing the repository by splitting out the GetCollection call then applying the filtering using standard LINQ, but I don't want to have to return my entire collection from the DB and then query it in the repository layer.

I've found several examples of people unit testing MongoDB, but these all involved mocking the FindAsync call, which is not what I need to do.

I also considered declaring my filter as an Expression<Func<Item, Boolean>> in a separate class, so I could test that in isolation, but I'd like to explore other options before going down that route, as it adds complexity to the repository layer.

Upvotes: 2

Views: 1420

Answers (1)

desertech
desertech

Reputation: 1029

I have not worked with MongoDB, but here's my approach from the perspective of pure unit testing:

  • Observe that method under test has four inputs: the IMongoClient dependency (which I consider as an indirect input) and three arguments (direct inputs).
  • Create an object implementing IMongoDatabase (MD) and mock IMongoClient to return it.
  • Create an object implementing IMongoCollection (MC) and have it returned by MD's GetCollection method; MC is actually your test data.
  • Define three arguments to pass to method under test.

Now you can perform the test.

Please notice that the implementation of MC includes methods such FindAsync, but that's ok since your purpose is not to test functioning of MongoDB, but to test flow and behavior of your method.

Take a look at this over-simplified example (based on your code):

    public async Task<IEnumerable<string>> GetItemsUsingFilter()
    {
        var cursor = await this._db.GetCollection<string>("Items")
            .SortByDescending()
            .Limit(10);

        return await cursor.ToListAsync();
    }

If GetCollection returns in production environment 12 items, "A".."L", we would want "L".."C" to be returned. Moreover, we want our test to fail if for example some other developer mistakenly moves Limit before SortByDescending (which will cause production code to return "J".."A").

Upvotes: 0

Related Questions