如何使用进程内迁移运行程序和内存中的SQLite数据库测试流畅的迁移

时间:2018-11-13 13:48:04

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

我刚刚开始在当前项目中使用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
    }
}

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

2 个答案:

答案 0 :(得分:0)

好的,我找到了解决方案。我必须使用跑步者处理器的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"));
}

答案 1 :(得分:0)

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

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

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

内存数据库中的 SQLite 存在的时间与连接一样长 - 乍一看,每个连接有 1 个数据库。 实际上,尽管根据我的实验,只要始终打开至少 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();
    });
}