我有一个 API,我想对其进行集成测试。我有一个 Program.cs 和一个定制的 Startup.cs,它基本上是一个静态类,其中包含
WebApplicationBuilder
的扩展方法,以模仿 .NET 6 之前的 .NET 行为。
// Program.cs
WebApplication.CreateBuilder(args)
.ConfigureLogging()
.ConfigureServices()
.Build()
.Initialize()
.ConfigurePipeline()
.Run();
// Startup.cs
public static class Startup
{
public static WebApplicationBuilder ConfigureLogging(this WebApplicationBuilder builder)
{
// Configure logging
return builder;
}
public static WebApplicationBuilder ConfigureServices(this WebApplicationBuilder builder)
{
var services = builder.Services;
var configuration = builder.Configuration;
services.AddPooledDbContextFactory<AppDbContext>((serviceProvider, options) =>
{
var connectionString = configuration.GetConnectionString("Database");
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.EnableDynamicJson();
options.UseNpgsql(dataSourceBuilder.Build());
});
return builder;
}
public static WebApplication ConfigurePipeline(this WebApplication app)
{
// Register middleware
app.UseRouting();
app.MapControllers();
return app;
}
public static WebApplication Initialize(this WebApplication app)
{
// Resolve DbContext and call .Migrate()
return app;
}
}
我想做的是创建一个自定义
WebApplicationFactory<>
并覆盖其中的一些内容,例如将使用的数据库。我正在使用 Testcontainers.PostgreSql NuGet 包,我的工厂如下所示:
public class MyFactory : WebApplicationFactory<IAssemblyMarker>, IAsyncLifetime
{
private readonly PostgreSqlContainer _postgreSqlContainer = new PostgreSqlBuilder()
.WithUsername("username")
.WithPassword("password")
.WithDatabase("mydb")
.Build();
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
});
// If I use ConfigureTestServices, the test first calls ConfigureTestServices,
// then ConfigureServices, and then Startup.ConfigureServices (the extension method)
//builder.ConfigureTestServices(services =>
builder.ConfigureServices(services =>
{
var connectionString = _postgreSqlContainer.GetConnectionString();
services.RemoveAll<AppDbContext>();
services.RemoveAll<IDbContextFactory<AppDbContext>>();
services.RemoveAll<IDataTypeBuilder>();
services.AddSingleton<IDataTypeBuilder, NpgsqlDataTypeBuilder>();
services.AddPooledDbContextFactory<AppDbContext>(options =>
{
var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString)
{
Name = "IntegrationTests App"
};
options.UseNpgsql(dataSourceBuilder.Build());
});
});
}
public async Task InitializeAsync()
{
await _postgreSqlContainer.StartAsync();
}
async Task IAsyncLifetime.DisposeAsync()
{
await _postgreSqlContainer.StopAsync();
}
}
问题是,调用
_myFactory.CreateClient()
总是会抛出异常,因为我的 appsettings.json
中的连接字符串是为我的本地 PostgreSQL 容器定义的,这自然会导致集成测试失败。我确实尝试设置断点,但有一个问题:为什么我的测试方法(调用 CreateClient 之后)输入 WebApplicationFactory<>.ConfigureServices
(或ConfigureTestServices - 我似乎找不到两者之间的区别),然后是 Startup.ConfigureServices
?然后发生的情况是,PostgreSqlContainer
连接字符串在我的测试中根本不起作用,但测试方法尝试连接到我的appsettings.json
中定义的数据库。
正确的修复和解决方法是什么?自定义 Startup 类可能是问题所在(因为
Startup.ConfigureServices
是自定义扩展方法)?
所以,我设法找到了一个“解决方法”(不过不知道是否应该这样做)。我相信因为我的
AppDbContext
是使用 AddPooledDbContextFactory<AppDbContext>
注册的,所以我还没有在 services.RemoveAll<>
/ WebApplicationFactory.ConfigureWebHost
内的 builder.ConfigureTestServices
中删除使用 builder.ConfigureServices
的所有服务注册(我仍然不知道两者之间有什么区别)会是),所以 AppDbContext
是在我的自定义 Startup
类中使用预先存在的 appsettings.json 配置注册的,之后,我尝试在我的 DbContextFactory
中重新注册 MyFactory
。
所以,到目前为止,我的解决方法是从我的
ConfigureTestServices
中完全删除ConfigureServices
和MyFactory : WebApplicationFactory
并调用ConfigureAppConfiguration
,这允许我删除任何IConfiguration
源并定义我自己的Dictionary<>
,这将有我的自定义应用程序配置的(集成测试特定)值。
builder.ConfigureAppConfiguration((ctx, builder) =>
{
var configuration = new Dictionary<string, string?>
{
["ConnectionStrings:Database"] = _postgreSqlContainer.GetConnectionString(),
};
builder.Sources.Clear();
builder.AddInMemoryCollection(configuration);
});
这样做的结果是,当我的
Startup.ConfigureServices
被我的Program
类调用时,它使用的IConfiguration
只有我上面定义的键值对,所以它尝试使用的连接字符串基本上是由我的集成测试预先设置的MyFactory
。
我会将此问题标记为已回答。如果将来有人有更好的答案或解释,请随时发布在这里。