IOption模式 - 单元测试和层次传递

时间:2017-07-10 23:06:31

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

我有一些代码(C#.Net Core WebAPI)我希望进行单元测试,但需要一些帮助,因为依赖对我来说有点奇怪。

代码来自一些示例代码(我在网上找到),用于使用.Net Core WebAPI访问MongoDb,最初看起来还不错,直到现在......

DbContext和Repository都具有相同的依赖关系 - 而Repository只是将其传递给DbContext - 因为Repository实例化了DbContext:

  public class LogItemRepository : ILogItemRepository
  {
    private readonly DbContext _context = null;

    public LogItemRepository(IOptions<DbSettings> settings)
    {
      _context = new DbContext(settings);
    }

...

  public class DbContext
  {
    private readonly IMongoDatabase _database = null;

    public DbContext(IOptions<DbSettings> settings)
    {
      var client = new MongoClient(settings.Value.ConnectionString);
      if (client != null)
        _database = client.GetDatabase(settings.Value.Database);
    }

    public IMongoCollection<LogItem> LogItemsCollection
    {
      get
      {
        return _database.GetCollection<LogItem>("LogItem");
      }
    }
  }
}

我不熟悉Options pattern,但从快速阅读看起来很不错。但我不相信做出子依赖(选项),父对象的依赖(如上例所示)是一种好习惯。

相反,我应该创建一个接口,IDbContext,并将其用作存储库的依赖项?这就是我过去所做的 - 但不确定这是否打破了期权模式。

我怀疑这是主观的,但我想要其他一些意见。

由于 添

1 个答案:

答案 0 :(得分:1)

虽然主要基于意见,但通常的做法是不在存储库的构造函数中实例化db上下文。这将存储库与上下文紧密联系在一起。按照您在OP中的说明注入抽象。

我可能会在这里分裂头发,但在提供的示例中仍然存在太多的紧耦合。

首先抽象上下文

public interface IDbContext {
    IMongoCollection<LogItem> LogItemsCollection { get; }
}

并且IMongoDatabase也是显式依赖

public class DbContext : IDbContext {
    private readonly IMongoDatabase database = null;

    public DbContext(IMongoDatabase database) 
        this.database = database;
    }

    public IMongoCollection<LogItem> LogItemsCollection {
        get {
            return database.GetCollection<LogItem>("LogItem");
        }
    }
}

使用组合根(Startup)中需要的选项来配置服务。您甚至会考虑将其封装在扩展方法中。

services.AddScoped<IMongoDatabase>(provider => {
    var settings = provider.GetService<IOptions<DbSettings>>();
    var client = new MongoClient(settings.Value.ConnectionString);
    return client.GetDatabase(settings.Value.Database);
});
services.AddScoped<IDbContext, DbContext>();
services.AddScoped<ILogItemRepository, LogItemRepository>();
//...NOTE: Use the desired service lifetime. This is just an example

这使得存储库显然依赖于上下文抽象

public class LogItemRepository : ILogItemRepository {
    private readonly IDbContext context = null;

    public LogItemRepository(IDbContext context) {
         this.context = context;
    }

    //...other code
}

现在所有图层都被解耦并明确说明它们的依赖关系,允许根据需要进行更多的隔离单元测试。