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().All()
.Do(b => b.BlogID = )
.Build(); var fakePosts = Builder<BlogPost>.CreateListOfSize().All()
.Do(x => x.Id = )
.TheFirst().With(x => x.BlogSite = blogSites[])
.TheNext().With(x => x.BlogSite = blogSites[])
.TheNext().With(x => x.BlogSite = blogSites[])
.Build(); efUnitOfWork.AddRange(fakePosts);
efUnitOfWork.SaveChanges(); return fakePosts;
}
}
}