如何使用 TestServer 和数据播种 class 到 运行 内存中的集成测试共享内存数据库

How to share in memory database using TestServer and data seeding class to run Integration Tests in memory

我最近开始使用 TestServer class 自托管和 bootstrap Aspnet Core API 运行 Integration Tests 没有专用的 运行宁环境。

我喜欢它的工作方式和使用自定义环境名称,我决定控制 EF 上下文的创建方式,从 SQL 服务器切换到内存数据库。

问题是,为了通过 API 请求播种 运行 测试所需的数据,编码和 运行 宁时间都将非常昂贵。

我的想法是创建一个 class 或一个简单的框架来植入每个测试所需的数据,但为此我需要使用相同的内存数据库,该数据库由 API 堆栈由 TestServer.

这怎么可能?

首先要明白,为了更好地测试替换关系数据库(例如 SQL 服务器),In Memory 数据库并不理想。

在种种限制中,不支持外键约束。 使用 in-memory 数据库的更好方法是使用 SQLite In-Memory mode.

这是我用来设置测试服务器、播种数据并为依赖注入注册数据库上下文的代码:

测试服务器

public class ApiClient {
    private HttpClient _client;

    public ApiClient()
    {
        var webHostBuilder = new WebHostBuilder();
        webHostBuilder.UseEnvironment("Test");
        webHostBuilder.UseStartup<Startup>();
        var server = new TestServer(webHostBuilder);
        _client = server.CreateClient();
    }

    public async Task<HttpResponseMessage> PostAsync<T>(string url, T entity)
    {
        var content = new StringContent(JsonConvert.SerializeObject(entity), Encoding.UTF8, "application/json");
        return await _client.PostAsync(url, content);
    }

    public async Task<T> GetAsync<T>(string url)
    {
        var response = await _client.GetAsync(url);
        response.EnsureSuccessStatusCode();
        var responseString = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(responseString);
    }
}

数据播种(助手class)

public class TestDataConfiguration
{
    public static IMyContext GetContex()
    {
        var serviceCollection = new ServiceCollection();
        IocConfig.RegisterContext(serviceCollection, "", null);
        var serviceProvider = serviceCollection.BuildServiceProvider();
        return serviceProvider.GetService<IMyContext>();
    }
}

数据播种(测试class)

[TestInitialize]
public void TestInitialize()
{
    _client = new ApiClient();
    var context = TestDataConfiguration.GetContex();
    var category = new Category
    {
        Active = true,
        Description = "D",
        Name = "N"
    };
    context.Categories.Add(category);
    context.SaveChanges();
    var transaction = new Transaction
    {
        CategoryId = category.Id,
        Credit = 1,
        Description = "A",
        Recorded = DateTime.Now
    };
    context.Transactions.Add(transaction);
    context.SaveChanges();
}

数据库上下文注册(在IocConfig.cs)

public static void RegisterContext(IServiceCollection services, string connectionString, IHostingEnvironment hostingEnvironment)
{
    if (connectionString == null)
        throw new ArgumentNullException(nameof(connectionString));
    if (services == null)
        throw new ArgumentNullException(nameof(services));

    services.AddDbContext<MyContext>(options =>
    {
        if (hostingEnvironment == null || hostingEnvironment.IsTesting())
        {
            var connection = new SqliteConnection("DataSource='file::memory:?cache=shared'");
            connection.Open();
            options.UseSqlite(connection);
            options.UseLoggerFactory(MyLoggerFactory);
        }
        else
        {
            options.UseSqlServer(connectionString);
            options.UseLoggerFactory(MyLoggerFactory);
        }
    });

    if (hostingEnvironment == null || hostingEnvironment.IsTesting())
    {
        services.AddSingleton<IMyContext>(service =>
        {
            var context = service.GetService<MyContext>();
            context.Database.EnsureCreated();
            return context;
        });
    } else {
        services.AddTransient<IMyContext>(service => service.GetService<MyContext>());
    }
 }

关键是 URI file string 用于创建 SQLite 连接: var connection = new SqliteConnection("DataSource='file::memory:?cache=shared'");