Home > Software design >  Fake MongoDB collection with FakeItEasy in C#
Fake MongoDB collection with FakeItEasy in C#

Time:09-02

I'm trying to write unit tests for my .NET Core API's service layer with NUnit and FakeItEasy and I have troubles with faking the connection to Mongo. The service is from the Microsoft documentation with small changes.

Here is my code:

IBaseService.cs

public interface IBaseService<T>
{
    public IMongoClient MongoClient { get; }
    public IMongoDatabase MongoDb { get; }
    public IMongoCollection<T> GetCollection();
}

IBoxService.cs defines some functions

public interface IBoxService : IBaseService<Box>

BoxService.cs

public class BoxService : IBoxService
{
    private readonly IOptions<DbSettings> _dbSettings;
    private readonly IMongoCollection<Box> _boxCollection;

    public BoxService(IOptions<DbSettings> dbSettings)
    {
        _dbSettings = dbSettings;
        MongoClient = new MongoClient(_dbSettings.Value.ConnectionString);

        MongoDb = MongoClient.GetDatabase(_dbSettings.Value.DatabaseName);

        _boxCollection = GetCollection();
    }

    public IMongoClient MongoClient { get; }
    public IMongoDatabase MongoDb { get; }
    public IMongoCollection<Box> GetCollection()
    {
        return MongoDb.GetCollection<Box>(_dbSettings.Value.BoxCollectionName);
    }

    public async Task<List<Box>> FindAllAsync() =>
        await _boxCollection.Find(_ => true).ToListAsync();
}

DbSettings.cs

public class DbSettings
{
    public string ConnectionString { get; set; } = null!;
    public string DatabaseName { get; set; } = null!;
    public string BoxCollectionName { get; set; } = null!;
}

And finally the BoxServiceTest.cs

public class BoxServiceTest
{
    private readonly IOptions<DbSettings> _fakeOptions;
    private readonly IMongoClient _mongoClient;
    private readonly IMongoDatabase _mongoDb;
    private readonly IMongoCollection<Box> _boxCollection;
    private readonly List<Box> _boxes;

    public BoxServiceTest()
    {
        _boxCollection = A.Fake<IMongoCollection<Box>>();
        _fakeOptions = A.Fake<IOptions<DbSettings>>();
        _mongoClient = A.Fake<MongoClient>();
        _mongoDb = A.Fake<IMongoDatabase>();
        
        _boxes = new List<Box> {...};
    }

    [Test]
    public async Task FindAllAsync_ReturnsAllBoxes()
    {
        // Arrange
        var dbSettings = new DbSettings
        {
            ConnectionString = "mongodb://localhost:27017",
            DatabaseName = "database",
            BoxCollectionName = "boxes"
        };
        A.CallTo(() => _fakeOptions.Value).Returns(dbSettings);
        
        var boxService = new BoxService(_fakeOptions);

        // Act
        var boxes = await boxService.FindAllAsync();

        // Assert
    }
}

Here the boxes contains the records from Mongo but I'd like it to have the values from the _boxes list instead of querying the db for every test.

I've tried faking some calls but without any success. Trying to fake the GetDatabase call:

A.CallTo(() => _mongoClient.GetDatabase(A<string>._, default)).Returns(_mongoDb);

But it gives an error:

The current proxy generator can not intercept the method MongoDB.Driver.IMongoClient.GetDatabase(System.String name, MongoDB.Driver.MongoDatabaseSettings settings) for the following reason: - Non-virtual members can not be intercepted. Only interface members and virtual, overriding, and abstract members can be intercepted.

What am I doing wrong? How should I fake the Mongo connection and the returned list?

CodePudding user response:

You have a typo in BoxServiceTest constructor. You should mock an interface:

_mongoClient = A.Fake<IMongoClient>();

CodePudding user response:

So I reworked my service and decided to use FindAsync instead of find. This article helped me with the 2nd part. Now the unit test is working and my code looks like this:

BoxService.cs

public class BoxService : IBoxService
{
    private readonly IMongoCollection<Box> _boxCollection;

    public BoxService(IOptions<DbSettings> dbSettings, IMongoClient mongoClient)
    {
        var mongoDatabase = mongoClient.GetDatabase(dbSettings.Value.DatabaseName);
        _boxCollection = mongoDatabase.GetCollection<Box>(dbSettings.Value.BoxCollectionName);
    }

    public async Task<List<Box>> FindAllAsync()
    {
        var cursor = await _boxCollection.FindAsync(_ => true);
        var boxList = await cursor.ToListAsync();
        return boxList;
    }
}

BoxServiceTest.cs

public class BoxServiceTest
{
    private readonly IMongoCollection<Box> _boxCollection;
    private readonly List<Box> _boxes;
    private readonly IBoxService _boxService;

    public BoxServiceTest()
    {
        _boxCollection = A.Fake<IMongoCollection<Box>>();
        _boxes = new List<Box> {...};

        var mockOptions = A.Fake<IOptions<DbSettings>>();
        var mongoClient = A.Fake<IMongoClient>();
        var mongoDb = A.Fake<IMongoDatabase>();

        A.CallTo(() => mongoClient.GetDatabase(A<string>._, default)).Returns(mongoDb);
        A.CallTo(() => mongoDb.GetCollection<Box>(A<string>._, default)).Returns(_boxCollection);
        _boxService = new BoxService(mockOptions, mongoClient);
    }

    [Test]
    public async Task FindAllAsync_ReturnsAllBoxes()
    {
        // Arrange
        var cursor = A.Fake<IAsyncCursor<Box>>();
        A.CallTo(() => cursor.Current).Returns(_boxes);
        A.CallTo(() => cursor.MoveNextAsync(default)).ReturnsNextFromSequence(true, false);
        A.CallTo(() => _boxCollection.FindAsync(A<FilterDefinition<Box>>._, A<FindOptions<Box, Box>>._, default))
            .Returns(cursor);

        // Act
        var boxList = await _boxService.FindAllAsync();

        // Assert
        Assert.That(boxList, Is.EqualTo(_boxes));
    }
}
  • Related