【问题标题】:How to properly mock MongoDbClient如何正确模拟 MongoDb 客户端
【发布时间】:2019-08-24 09:07:29
【问题描述】:

上下文

我正在为我一直在开发的 API 编写单元测试,但在尝试对访问 MongoDB 存储的“上下文”进行单元测试时遇到了一个问题。

我为我的上下文抽象了当前接口:

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

我能够成功地使用这个接口,连同Moq 对我的存储库进行单元测试。

但是,在尝试对我的 Context 的实现进行单元测试时,我无法找到一个模拟内部的解决方案:

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;
        }
    }
}

有问题的单元测试是:

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
}

问题

我能想到的唯一解决方案是将我的ProjectsContext 更改为具有接收作为参数的构造函数,IMongoDatabase 将被使用。然而,这是唯一的解决方案吗?

使用的库

我正在为我的单元测试和实现使用以下 NuGet:

  • xUnit
  • Coverlet.msbuild
  • 起订量
  • 自动夹具
  • FluentAssertions
  • MongoDB

【问题讨论】:

  • MongoClient是你创建的类吗?
  • MongoClient 由 MongoDB.Driver 为 CSharp 提供
  • @AlvesRC 您的ProjectsContext 与实现问题MongoClient 紧密耦合,这使得对其进行隔离测试变得困难。 IMongoDatabase 是真正的依赖,应该显式注入到目标类中。
  • @Nkosi 这就是我的想法,尽管我希望可能有更好的方法哈哈。您能否发布一个答案,以便我将其选为正确答案?
  • @AlvesRC 这是一种更可靠的方法。

标签: c# mongodb unit-testing asp.net-core


【解决方案1】:

ProjectsContext 与实现问题/细节紧密耦合(即: MongoClient,这使得对其进行隔离测试变得困难。

IMongoDatabase 是真正的依赖,应该显式注入到目标类中。

参考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;
        }
    }
}

至于数据库的创建/初始化,那个实现细节可以移到组合根目录

//...ConfigureServices

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

//...

现在可以单独完成目标类的测试,而不会出现来自第三方实施问题的意外行为

[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);
}

【讨论】:

  • 该解决方案按预期工作,但是我更改了服务的注册方式。我将 IMongoClient 作为 Singleton 添加到我的 DI 集合中,然后将 IMongoDatabase 添加为使用所需 IMongoClient 的 Scope,最后,将我的 IProjectsContext 及其实现添加到 DI。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多