简体   繁体   中英

How to properly mock MongoDbClient

Context

I'm writing unit tests for an API I've been developing and I just ran into an issue while trying to UnitTest a "Context" for accessing a MongoDB Storage.

I abstracted the current interface for my context:

public interface IProjectsContext
{
    IMongoCollection<Project> Projects { get; }
}

I'm able to successfully use this interface, together with Moq to UnitTest my Repositories.

However, when trying to UnitTest my Context's implementation I've been unable to muster a solution for mocking the inwards:

public class ProjectsContext : IProjectsContext
{
    private const string ProjectsCollectionName = "Projects";

    private readonly IDatabaseParameters _dbParams;
    private readonly MongoClient _client;
    private readonly IMongoDatabase _database;

    private IMongoCollection<Project> _projects;

    public ProjectsContext(IDatabaseParameters dbParams)
    {
        _dbParams = dbParams ?? throw new ArgumentNullException(nameof(dbParams));
        _client = new MongoClient(_dbParams.ConnectionString);
        _database = _client.GetDatabase(_dbParams.DatabaseName);
    }

    public IMongoCollection<Project> Projects
    {
        get
        {
            if (_projects is null)
                _projects = _database.GetCollection<Project>(ProjectsCollectionName);
            return _projects;
        }
    }
}

The unit test in question is:

private readonly Fixture _fixture = new Fixture();
private readonly Mock<IDatabaseParameters> _dbParametersMock = new Mock<IDatabaseParameters>();

public ProjectsContextTests()
{

}

[Fact(DisplayName = "Create a Project Context")]
public void CreateProjectContext()
{
    // Arrange
    _dbParametersMock.Setup(m => m.ConnectionString).Returns(_fixture.Create<string>());
    _dbParametersMock.Setup(m => m.DatabaseName).Returns(_fixture.Create<string>());

    // Act
    var result = new ProjectsContext(_dbParametersMock.Object);

    // Assert
    result.Should().NotBeNull();
    result.Should().BeAssignableTo<IProjectsContext>();
    // TODO: Write a test to assert the ProjectCollection
}

Question

The only solution I can think of is changing my ProjectsContext to have a constructor with receives, as a parameter, the IMongoDatabase which is going to be used. However, is this the only solution?

Libraries used

I'm using the following NuGets for my UnitTests and Implementation:

  • xUnit
  • Coverlet.msbuild
  • Moq
  • AutoFixture
  • FluentAssertions
  • MongoDB

ProjectsContext is tightly coupled to implementation concerns/details (ie: MongoClient ) that make testing it isolation difficult.

IMongoDatabase is the true dependency and should be explicitly injected into the target class.

Reference Explicit Dependencies Principle

public class ProjectsContext : IProjectsContext {
    private const string ProjectsCollectionName = "Projects";
    private readonly IMongoDatabase database;
    private IMongoCollection<Project> projects;

    public ProjectsContext(IMongoDatabase database) {
        this.database = database;
    }

    public IMongoCollection<Project> Projects {
        get {
            if (projects is null)
                projects = database.GetCollection<Project>(ProjectsCollectionName);
            return projects;
        }
    }
}

As for the creation/initialization of the database, that implementation detail can be moved to the composition root

//...ConfigureServices

services.AddScoped<IMongoDatabase>(sp => {
    var dbParams = sp.GetRequiredService<IDatabaseParameters>();
    var client = new MongoClient(dbParams.ConnectionString);
    return client.GetDatabase(dbParams.DatabaseName);
});

//...

Testing of the target class can now be done in isolation without unexpected behavior from 3rd party implementation concerns

[Fact(DisplayName = "Create a Project Context")]
public void CreateProjectContext() {
    // Arrange
    var collectionMock = Mock.Of<IMongoCollection<Project>>();
    var dbMock = new Mock<IMongoDatabase>();
    dbMock.Setup(_ => _.GetCollection<Project>(It.IsAny<string>()))
        .Returns(collectionMock);

    // Act
    var result = new ProjectsContext(dbMock.Object);

    // Assert
    result.Should().NotBeNull()
        .And.BeAssignableTo<IProjectsContext>();
    //Write a test to assert the ProjectCollection
    result.Projects.Should().Be(collectionMock);
}

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