ASP.NET Core 集成测试中结合 WebApplicationFactory 使用 SQLite 内存数据库

SQLite 内存数据库(in-memory database)的连接字符串是  Data Source=:memory: ,它的特点是数据库连接一关闭,数据库就会被删除。而使用  services.AddDbContext 通过连接字符串配置 EF Core 时,EF Core 会在每次查询或 SaveChanges 后立即关闭数据库连接。在这样的情况下,集成测试中就无法在向 SQLite 内存数据库写入数据库后进行查询测试。

为了解决上述问题,我们就不能让 EF Core 自己自动维护数据库连接,而只能改为手动模式,手工创建并打开 SqliteConnection 给 EF Core 使用,在用完之后的适当时候关闭连接。

除此之外,由于在每次打开数据库连接都会创建新的数据库,所以还要解决在什么写入数据之前完成数据库的初始化。

结合 WebApplicationFactory ,我们用下面继承自 WebApplicationFactory 的实现代码解决了问题。

public class BlogWebAppFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    private DbConnection _dbConnection;

    public BlogWebAppFactory()
    { }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        base.ConfigureWebHost(builder);

        builder.ConfigureServices(services =>
        {
            _dbConnection = new SqliteConnection("Data Source=:memory:");
            _dbConnection.Open();
            services.AddDbContext<EfUnitOfWork>(options =>
            {
                options.UseSqlite(_dbConnection);
            });
        });
    }

    protected override TestServer CreateServer(IWebHostBuilder builder)
    {
        var server = base.CreateServer(builder);

        using (var scope = server.Host.Services.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<EfUnitOfWork>();
            dbContext.Database.EnsureCreated();
        }

        return server;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _dbConnection?.Dispose();
    }
}

集成测试中的示例代码如下

public class PostsWebApiTests : IClassFixture<BlogWebAppFactory<Startup>>
{
    private readonly BlogWebAppFactory<Startup> _factory;
    private readonly HttpClient _httpClient;

    public PostsWebApiTests(BlogWebAppFactory<Startup> factory)
    {
        _factory = factory;
        _httpClient = factory.CreateClient();
    }

    [Fact]
    public async Task GetPostsByBlogIdsTest()
    {
        var fakePosts = SeedData();
        var blogIds = fakePosts.Select(p => p.BlogID).Distinct();           
        var response = await _httpClient.PostAsJsonAsync($"/blogposts/blogIds", blogIds);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }

    private IList<BlogPost> SeedData()
    {
        using (var scope = _factory.Server.Host.Services.CreateScope())
        {
            var efUnitOfWork = scope.ServiceProvider.GetRequiredService<EfUnitOfWork>();

            var blogSites = Builder<BlogSite>.CreateListOfSize(3).All()
            .Do(b => b.BlogID = 0)
            .Build();

            var fakePosts = Builder<BlogPost>.CreateListOfSize(12).All()
            .Do(x => x.Id = 0)
            .TheFirst(4).With(x => x.BlogSite = blogSites[0])
            .TheNext(4).With(x => x.BlogSite = blogSites[1])
            .TheNext(4).With(x => x.BlogSite = blogSites[2])
            .Build();

            efUnitOfWork.AddRange(fakePosts);
            efUnitOfWork.SaveChanges();

            return fakePosts;
        }
    }
}

 

posted @ 2018-10-10 14:02 dudu 阅读(...) 评论(...) 编辑 收藏