Alv*_* RC 2 c# unit-testing mongodb asp.net-core
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; }
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)
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?
I'm using the following NuGets for my UnitTests and Implementation:
ProjectsContext紧紧地耦合到实施顾虑/细节(即: MongoClient),使测试它隔离困难。
IMongoDatabase 是真正的依赖项,应该显式地注入到目标类中。
参考显式依赖原则
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
至于数据库的创建/初始化,该实现细节可以移动到组合根
//...ConfigureServices
services.AddScoped<IMongoDatabase>(sp => {
var dbParams = sp.GetRequiredService<IDatabaseParameters>();
var client = new MongoClient(dbParams.ConnectionString);
return client.GetDatabase(dbParams.DatabaseName);
});
//...
Run Code Online (Sandbox Code Playgroud)
现在可以独立完成目标类的测试,而不会出现来自 3rd 方实现问题的意外行为
[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);
}
Run Code Online (Sandbox Code Playgroud)