我在ASP.NET Core Web应用程序中支持多个租户的博客中已经指出,租户可以使用单独的数据库。现在是时候为数据上下文编写一些测试,以确保在意外情况下行为正确。这篇文章涵盖数据环境和多租户的单元测试。
我以前的多租户工作
为了更好地了解我以前在ASP.NET Core和Entity Framework Core 2.0中的多租户工作,请仔细阅读以下文章:
Entity Framework Core 2.0中的全局查询过滤器
在 ASP.NET Core中执行租户服务
在ASP.NET Core上实施每个租户策略的数据库
数据库上下文
为了让阅读更容易,我在这里给出了在以前的帖子中使用的简化版数据上下文
public class PlaylistContext : DbContext { private readonly Tenant _tenant; public DbSet<Playlist> Playlists { get; set; } public DbSet<Song> Songs { get; set; } public PlaylistContext(DbContextOptions<PlaylistContext> options, ITenantProvider tenantProvider) : base(options) { _tenant = tenantProvider.GetTenant(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(_tenant.DatabaseConnectionString); base.OnConfiguring(optionsBuilder); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Playlist>().HasKey(e => e.Id); modelBuilder.Entity<Song>().HasKey(e => e.Id); base.OnModelCreating(modelBuilder); } } |
由于每个租户都有单独的数据库,因此无需通过全局查询过滤器自动强制租户ID到所有域实体。
要测试什么?
下面的数据库上下文使用租户提供者来检测租户必须使用的数据库的连接字符串。由于依赖租户提供商,因此编写一些测试以确保数据上下文类的行为像预期一样非常重要。那么可能出现什么问题呢?
那么,单元测试有几件事要考虑:
租户提供者为null,
租户为null,
缺少数据库连接字符串.
其实最后一件事情不应该发生,但世事无绝对。我们可能通过自定义构造的DTO列表序列化云端的租户,或者手动修改,导致链接字符串缺失,因此测试缺少的连接字符串是合理的。
添加验证
在编写测试之前,我们将对无效数据的验证添加到数据上下文类中。以下代码涵盖以上所有三项检查。
public class PlaylistContext : DbContext { private readonly Tenant _tenant; public DbSet<Playlist> Playlists { get; set; } public DbSet<Song> Songs { get; set; } public PlaylistContext(DbContextOptions<PlaylistContext> options, ITenantProvider tenantProvider) : base(options) { if(tenantProvider == null) { throw new ArgumentNullException(nameof(tenantProvider)); } _tenant = tenantProvider.GetTenant(); if(_tenant == null) { throw new NullReferenceException("Tenant is null"); } } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if(string.IsNullOrEmpty(_tenant.DatabaseConnectionString)) { throw new NullReferenceException("Connection string is missing"); } optionsBuilder.UseSqlServer(_tenant.DatabaseConnectionString); base.OnConfiguring(optionsBuilder); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Playlist>().HasKey(e => e.Id); modelBuilder.Entity<Song>().HasKey(e => e.Id); base.OnModelCreating(modelBuilder); } } |
编写单元测试
为了测试数据上下文,在测试项目中我们需要一些额外的类。在单元测试中,系统的其他部分不应该发生集成,否则它们是集成测试。由于依赖于ITenantProvider接口,因此单元测试需要假的租户提供商。
public class FakeTenantProvider : ITenantProvider { private Tenant _tenant; public FakeTenantProvider(Tenant tenant) { _tenant = tenant; } public Tenant GetTenant() { return _tenant; } } |
还有一个问题因为接口ITenantProvider中的OnConfiguring()方法是受保护的。所以外部调用者只能通过反射来调用此方法。或者扩展原始数据上下文,并添加从基类调用受保护的OnConfiguring()方法的新实例。
public class FakePlaylistContext : PlaylistContext { public FakePlaylistContext(DbContextOptions<PlaylistContext> options, ITenantProvider tenantProvider) : base(options, tenantProvider) { } public new void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); } } |
使用这两个类,现在可以编写数据上下文的测试。
[TestClass] public class PlaylistContextTests { [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void ThrowsExpetionIfTenantProviderIsNull() { var options = new DbContextOptions<PlaylistContext>(); new PlaylistContext(options, null); } [TestMethod] [ExpectedException(typeof(NullReferenceException))] public void ThrowsExceptionIfTenantIsNull() { var options = new DbContextOptions<PlaylistContext>(); var provider = new FakeTenantProvider(null); new PlaylistContext(options, provider); } [TestMethod] [ExpectedException(typeof(NullReferenceException))] public void ThrowsExceptionIfConnectionStringIsMissing() { var options = new DbContextOptions<PlaylistContext>(); var tenant = new Tenant { Id = 1 }; var provider = new FakeTenantProvider(tenant); var builder = new DbContextOptionsBuilder(); var context = new FakePlaylistContext(options, provider); context.OnConfiguring(builder); } } |
这些测试使用的是Microsoft测试工具,也可以在其他平台上使用ASP.NET Core项目。在Visual Studio中运行这些测试结果如下。
所有通过的测试成功完成。
总结
向Entity Framework Core数据上下文注入的租户提供者引入了需要进行测试的情况。为了测试上下文,需要一个伪类和数据上下文的继承版本。由于没有复杂的对象图来模拟测试,所以测试很简单。但这些测试必须确保数据上下文处理有问题的情况。
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。