ASP.NET Core / EF Core / xUnit.NET 集成测试中每个测试的种子测试数据
Seed test data for every test in ASP.NET Core / EF Core / xUnit.NET integration tests
我一直在遵循 ASP.NET Core 2.2 API 设置测试的策略,使用位于 Integration tests in ASP.NET Core.
的 Microsoft 文档
总而言之,我们扩展和自定义 WebApplicationFactory
并使用 IWebHostBuilder
来设置和配置各种服务,以使用内存数据库为我们提供数据库上下文进行测试,如下所示(复制并从文章中粘贴):
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context (ApplicationDbContext) using an in-memory
// database for testing.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
// Ensure the database is created.
db.Database.EnsureCreated();
try
{
// Seed the database with test data.
Utilities.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the " +
"database with test messages. Error: {ex.Message}");
}
}
});
}
}
在测试中,我们可以像这样使用工厂并创建客户端:
public class IndexPageTests :
IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<RazorPagesProject.Startup>
_factory;
public IndexPageTests(
CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
[Fact]
public async Task Test1()
{
var response = await _client.GetAsync("/api/someendpoint");
}
}
这工作正常,但请注意对 InitializeDbForTests
的调用,它会在配置服务时为所有测试设置一些测试数据。
我想要一个合理的策略来从头开始每个 API 测试,这样测试就不会相互依赖。我一直在寻找各种方法来获取 ApplicationDbContext
在我的测试方法中无济于事。
在彼此完全隔离的情况下进行集成测试是否合理,我如何使用 ASP.NET Core / EF Core / xUnit.NET 来处理它?
具有讽刺意味的是,您正在寻找 EnsureDeleted
而不是 EnsureCreated
。这将转储数据库。由于内存中 "database" 是无模式的,您实际上不需要确保它已创建甚至迁移它。
此外,您不应为内存数据库使用硬编码名称。这实际上会导致内存中的同一个数据库实例在任何地方都被使用。相反,您应该使用一些随机的东西:Guid.NewGuid().ToString()
就足够了。
实际上,Testing with InMemory 在标题为 "Writing Tests" 的部分中很好地描述了该过程。这是一些说明基本思想的代码
[TestClass]
public class BlogServiceTests
{
[TestMethod]
public void Add_writes_to_database()
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase(databaseName: "Add_writes_to_database")
.Options;
想法是每个测试方法都有一个单独的数据库,因此您不必担心测试 运行 的顺序或它们 运行 并行进行的事实.当然,您必须添加一些代码来填充您的数据库并从每个测试方法中调用它。
我已经使用过这种技术并且效果很好。
好的,我成功了!获得范围内的服务是关键。当我想从头开始播种时,我可以通过将播种调用包装在 using (var scope = _factory.Server.Host.Services.CreateScope()) { }
部分来开始每个测试,我可以先 var scopedServices = scope.ServiceProvider;
然后 var db = scopedServices.GetRequiredService<MyDbContext>();
在 db.Database.EnsureDeleted()
之前,最后运行 我的播种功能。有点笨重,但它的工作原理。
感谢 Chris Pratt 的帮助(来自评论的回答)。
我一直在遵循 ASP.NET Core 2.2 API 设置测试的策略,使用位于 Integration tests in ASP.NET Core.
的 Microsoft 文档总而言之,我们扩展和自定义 WebApplicationFactory
并使用 IWebHostBuilder
来设置和配置各种服务,以使用内存数据库为我们提供数据库上下文进行测试,如下所示(复制并从文章中粘贴):
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context (ApplicationDbContext) using an in-memory
// database for testing.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database
// context (ApplicationDbContext).
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<ApplicationDbContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
// Ensure the database is created.
db.Database.EnsureCreated();
try
{
// Seed the database with test data.
Utilities.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the " +
"database with test messages. Error: {ex.Message}");
}
}
});
}
}
在测试中,我们可以像这样使用工厂并创建客户端:
public class IndexPageTests :
IClassFixture<CustomWebApplicationFactory<RazorPagesProject.Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<RazorPagesProject.Startup>
_factory;
public IndexPageTests(
CustomWebApplicationFactory<RazorPagesProject.Startup> factory)
{
_factory = factory;
_client = factory.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false
});
}
[Fact]
public async Task Test1()
{
var response = await _client.GetAsync("/api/someendpoint");
}
}
这工作正常,但请注意对 InitializeDbForTests
的调用,它会在配置服务时为所有测试设置一些测试数据。
我想要一个合理的策略来从头开始每个 API 测试,这样测试就不会相互依赖。我一直在寻找各种方法来获取 ApplicationDbContext
在我的测试方法中无济于事。
在彼此完全隔离的情况下进行集成测试是否合理,我如何使用 ASP.NET Core / EF Core / xUnit.NET 来处理它?
具有讽刺意味的是,您正在寻找 EnsureDeleted
而不是 EnsureCreated
。这将转储数据库。由于内存中 "database" 是无模式的,您实际上不需要确保它已创建甚至迁移它。
此外,您不应为内存数据库使用硬编码名称。这实际上会导致内存中的同一个数据库实例在任何地方都被使用。相反,您应该使用一些随机的东西:Guid.NewGuid().ToString()
就足够了。
实际上,Testing with InMemory 在标题为 "Writing Tests" 的部分中很好地描述了该过程。这是一些说明基本思想的代码
[TestClass]
public class BlogServiceTests
{
[TestMethod]
public void Add_writes_to_database()
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseInMemoryDatabase(databaseName: "Add_writes_to_database")
.Options;
想法是每个测试方法都有一个单独的数据库,因此您不必担心测试 运行 的顺序或它们 运行 并行进行的事实.当然,您必须添加一些代码来填充您的数据库并从每个测试方法中调用它。
我已经使用过这种技术并且效果很好。
好的,我成功了!获得范围内的服务是关键。当我想从头开始播种时,我可以通过将播种调用包装在 using (var scope = _factory.Server.Host.Services.CreateScope()) { }
部分来开始每个测试,我可以先 var scopedServices = scope.ServiceProvider;
然后 var db = scopedServices.GetRequiredService<MyDbContext>();
在 db.Database.EnsureDeleted()
之前,最后运行 我的播种功能。有点笨重,但它的工作原理。
感谢 Chris Pratt 的帮助(来自评论的回答)。