【问题标题】:How to test fluent migrations with an in-process migration runner and a in memory SQLite database如何使用进程内迁移运行程序和内存 SQLite 数据库测试流畅的迁移
【发布时间】:2018-11-13 13:48:03
【问题描述】:

我刚刚开始在我当前的项目中使用 FluentMigration。我编写了我的第一个迁移,但我在为它编写单元测试时遇到了一些麻烦。

这里是一些示例代码:

private ServiceProvider CreateServiceProvider()
{
    return new ServiceCollection()
        .AddLogging(lb => lb.AddFluentMigratorConsole())
        .AddFluentMigratorCore()
        .ConfigureRunner(
            builder => builder
                .AddSQLite()
                    .WithGlobalConnectionString("Data Source=:memory:;Version=3;New=True;")
                    .WithMigrationsIn(typeof(MigrationOne).Assembly))
            .BuildServiceProvider();
}

private void PerformMigrateUp(IServiceScope scope)
{
    var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();

    runner.MigrateUp(1);
}

[Test]
public void ShouldHaveTablesAfterMigrateUp()
{
    var provider = this.CreateServiceProvider();

    using (var scope = provider.CreateScope())
    {
        this.PerformMigrateUp(scope);

        // here I'd like to test if tables have been created in the database by the migration
    }
}

我不知道如何(或是否可能)访问当前的数据库连接,以便执行查询。任何的意见都将会有帮助。谢谢。

【问题讨论】:

    标签: database sqlite unit-testing nunit fluent-migrator


    【解决方案1】:

    好的,我找到了解决方案。我必须使用 runner 处理器的 Process 方法来执行我自己的 sql 查询。

    看起来像这样:

    private ServiceProvider CreateServiceProvider()
    {
        return new ServiceCollection()
            .AddLogging(lb => lb.AddFluentMigratorConsole())
            .AddFluentMigratorCore()
            .ConfigureRunner(
                builder => builder
                    .AddSQLite()
                    .WithGlobalConnectionString(@"Data Source=:memory:;Version=3;New=True;")
                    .WithMigrationsIn(typeof(MigrationDate20181026113000Zero).Assembly))
            .BuildServiceProvider();
    }
    
    [Test]
    public void ShouldHaveNewVersionAfterMigrateUp()
    {
        var serviceProvider = this.CreateServiceProvider();
        var scope = serviceProvider.CreateScope();
        var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
    
        runner.MigrateUp(1);
    
        string sqlStatement = "SELECT Description FROM VersionInfo";
    
        DataSet dataSet = runner.Processor.Read(sqlStatement, string.Empty);
    
        Assert.That(dataSet, Is.Not.Null);
        Assert.That(dataSet.Tables[0].Rows[0].ItemArray[0], Is.EqualTo("Migration1"));
    }
    

    【讨论】:

      【解决方案2】:

      这是一个老问题,但很重要。我觉得很奇怪,我找不到任何关于此的文档。

      无论如何,这是我的解决方案,我发现它会更好一些,因为您不需要依赖跑步者。由于您不需要为构造函数参数打开大量选项。

      首先确保你安装了 Microsoft.Data.Sqlite 否则你会得到一个奇怪的错误。

      只要连接存在,内存数据库中的 SQLite 就存在 - 乍一看,每个连接都有 1 个数据库。 实际上,根据我的实验,只要至少有一个连接始终处于打开状态,就有一种方法可以在连接之间共享数据库。你只需要命名它。 https://docs.microsoft.com/en-us/dotnet/standard/data/sqlite/connection-strings#sharable-in-memory

      因此,我首先创建了一个连接,该连接将保持打开状态,直到测试完成。它将使用Guid.NewGuid() 命名,以便后续连接按预期工作。

      var dbName = Guid.NewGuid().ToString();
      var connectionString = $"Data Source={dbName};Mode=Memory;Cache=Shared";
      var connection = new SqliteConnection(connectionString);
      connection.Open();
      

      之后运行迁移的关键与之前回答的相同,但连接字符串使用命名数据库:

      var sp = services.AddFluentMigratorCore()
          .ConfigureRunner(fluentMigratorBuilder => fluentMigratorBuilder
              .AddSQLite()
              .WithGlobalConnectionString(connectionString)
              .ScanIn(AssemblyWithMigrations).For.Migrations()
          )
          .BuildServiceProvider();
      
      var runner = sp.GetRequiredService<IMigrationRunner>();
      runner.MigrateUp();
      

      这是我用来在需要连接到数据库正常执行的任何地方注入连接工厂的类:

      internal class PostgresConnectionFactory : IConnectionFactory
      {
          private readonly string connectionString;
      
          public PostgresConnectionFactory(string connectionString)
          {
              this.connectionString = connectionString;
          }
      
          public DbConnection Create()
          {
              return new NpgsqlConnection(connectionString);
          }
      }
      

      我只是将这个 (所有冰雹依赖倒置) 替换为:

      internal class InMemoryConnectionFactory : IConnectionFactory
      {
          private readonly string connectionstring;
      
          public InMemoryConnectionFactory(string connectionstring)
          {
              this.connectionstring = connectionstring;
          }
      
          public DbConnection Create()
          {
              return new SqliteConnection(connectionstring);
          }
      }
      

      连接字符串与我上面定义的名称相同。

      现在您可以在任何需要连接到相同内存数据库的地方简单地使用该连接工厂,并且因为我们现在可以连接多次进行集成测试。

      这是我的大部分实现:

      public static IDisposable CreateInMemoryDatabase(Assembly AssemblyWithMigrations, IServiceCollection services = null)
      {
          if (services == null)
              services = new ServiceCollection();
      
          var connectionString = GetSharedConnectionString();
          var connection = GetPersistantConnection(connectionString);
          MigrateDb(services, connectionString, AssemblyWithMigrations);
      
          services.AddSingleton<IConnectionFactory>(new InMemoryConnectionFactory(connectionString));
      
          return services.BuildServiceProvider()
              .GetRequiredService<IDisposableUnderlyingQueryingTool>();
      }
      
      private static string GetSharedConnectionString()
      {
          var dbName = Guid.NewGuid().ToString();
          return $"Data Source={dbName};Mode=Memory;Cache=Shared";
      }
      
      private static void MigrateDb(IServiceCollection services, string connectionString, Assembly assemblyWithMigrations)
      {
          var sp = services.AddFluentMigratorCore()
              .ConfigureRunner(fluentMigratorBuilder => fluentMigratorBuilder
                  .AddSQLite()
                  .WithGlobalConnectionString(connectionString)
                  .ScanIn(assemblyWithMigrations).For.Migrations()
              )
              .BuildServiceProvider();
      
          var runner = sp.GetRequiredService<IMigrationRunner>();
          runner.MigrateUp();
      }
      
      private static IDbConnection GetPersistantConnection(string connectionString)
      {
          var connection = new SqliteConnection(connectionString);
          connection.Open();
      
          return connection;
      }
      

      然后这里是一个示例测试:

      public Test : IDisposable {
          private readonly IDisposable _holdingConnection;
          
          public Test() {
              _holdingConnection = CreateInMemoryDatabase(typeof(MyFirstMigration).Assembly);
          }
          
          public void Dispose() {
              _holdingConnection.Dispose();
          }
      }
      

      您可能会注意到静态工厂返回一个自定义接口。它只是一个接口,扩展了我注入到存储库的常规工具,但也实现了 IDisposable。

      未测试的集成测试奖励,您将通过 WebApplicationFactory 或 TestServer 等创建服务集合:

      public void AddInMemoryPostgres(Assembly AssemblyWithMigrations)
      {
          var lifetime = services.BuildServiceProvider().GetService<IHostApplicationLifetime>();
      
          var holdingConnection= InMemoryDatabaseFactory.CreateInMemoryDapperTools(AssemblyWithMigrations, services);
      
          lifetime.ApplicationStopping.Register(() => {
              holdingConnection.Dispose();
          });
      }
      

      【讨论】:

        猜你喜欢
        • 2016-12-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-14
        • 1970-01-01
        • 2012-08-16
        • 1970-01-01
        相关资源
        最近更新 更多