简体   繁体   中英

How to Mock IMongoCollection.Find using Moq

I am struggling in mocking IMongoCollection.Find method using Moq for my unit tests.

What I tried:

  Mock<IMongoCollection<Person>> mockIMongoCollection = new Mock<IMongoCollection<Person>>();
  mockIMongoCollection.SetupAllProperties();
  mockIMongoCollection
            .Setup(x => x.Find(
                It.IsAny<FilterDefinition<Person>>(),
                It.IsAny<FindOptions>()))
            .Returns();

The thing is whatever I try to return using Returns() , it is not working, I expect to be able to return something convertible to List<Person> and I can't mock or create an instance of IFindFluent<Person,Person> as suggested by return type of Find method.

Maby this will help you.

I had to Mock such query:

 var queryRecords = await queryCollection
                .Find(filters)
                .Project(projection)
                .Skip(queryCriteria.Skip)
                .Limit(queryCriteria.Limit)
                .Sort(sort);

to do that I created an abstraction for MongoCollection to handle mongo queries.

public interface IFakeMongoCollection : IMongoCollection<BsonDocument>
{
    IFindFluent<BsonDocument, BsonDocument> Find(FilterDefinition<BsonDocument> filter, FindOptions options);

    IFindFluent<BsonDocument, BsonDocument> Project(ProjectionDefinition<BsonDocument, BsonDocument> projection);

    IFindFluent<BsonDocument, BsonDocument> Skip(int skip);

    IFindFluent<BsonDocument, BsonDocument> Limit(int limit);

    IFindFluent<BsonDocument, BsonDocument> Sort(SortDefinition<BsonDocument> sort);
}

So my tests setup looks like this

[TestFixture]
class QueryControllerTests
{
    private IOptions<MongoSettings> _mongoSettings;
    private QueryController _queryController;
    private Mock<IFakeMongoCollection > _fakeMongoCollection;
    private Mock<IMongoDatabase> _fakeMongoDatabase;
    private Mock<IMongoContext> _fakeMongoContext;
    private Mock<IFindFluent<BsonDocument, BsonDocument>> _fakeCollectionResult;

    [OneTimeSetUp]
    public void Setup()
    {
        _fakeMongoCollection = new Mock<IFakeMongoCollection >();
        _fakeCollectionResult = new Mock<IFindFluent<BsonDocument, BsonDocument>>(
        _fakeMongoDatabase = new Mock<IMongoDatabase>();
        _fakeMongoDatabase
            .Setup(_ => _.GetCollection<BsonDocument>("Test", It.IsAny<MongoCollectionSettings>()))
            .Returns(_fakeMongoCollection.Object);

        _fakeMongoContext = new Mock<IMongoContext>();
        _fakeMongoContext.Setup(_ => _.GetConnection()).Returns(_fakeMongoDatabase.Object);        
        var configuration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", false)
            .Build();
        _mongoSettings = Options.Create(configuration.GetSection("MongoConnection").Get<MongoSettings>());
        _queryController = new QueryController(_mongoSettings, _fakeMongoContext.Object);
    }
}

I hope it helps.

I know the question is a little old, but a had recently the same problem on time to developed unit test mocking mongo driver and I didn't find a concrete way to mock this method. So here is my solution:

As mentioned, it is a static method that could not be mocked, you should mock the non-static methods that internally will be called.

Find method will create and return an instance of FindFluent, which implements the IFindFluent interface (and in extension the IAsyncCursorSource one) that will use the non static collection method, and the static extension methods of this interfaces.

So you should find and analyze the method you will use after find, and mock components it use. For example, to load results into a list:

public List<Person> LoadPeople(IMongoCollection<Person> collection)
{
  return collection.find(Builders<Person>.Filter.Empty).ToList();
}

To Test it:

[Fact]
public void LoadPeopleTest()
{
  var mockIMongoCollection = new Mock<IMongoCollection<Person>>();
  var asyncCursor = new Mock<IAsyncCursor<Person>>();

  var expectedResult = fixture.CreateMany<Person>(5);
  
  mockIMongoCollection.Setup(_collection => _collection.FindSync(
      Builders<Person>.Filter.Empty,
      It.IsAny<FindOptions<Person>>(),
      default))
    .Returns(asyncCursor.Object);

  asyncCursor.SetupSequence(_async => _async.MoveNext(default)).Returns(true).Returns(false);
  asyncCursor.SetupGet(_async => _async.Current).Returns(expectedResult);

  var result = LoadPeople(mockIMongoCollection.Object);
  
  Assert.Equals(expectedResult, result);
}

Involved classes:

https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver/IMongoCollectionExtensions.cs

https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver/FindFluent.cs

https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver.Core/IAsyncCursorSource.cs

If you're only interested in testing the filtering functionality, you don't need to mock IMongoCollection you could just mock IQueriable instead and have your classes work with IQueriable<T> instead of IMongoQueryable<T> . Please, see my answer to this SO question for the code example.

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