如何在.NET API集成测试中修改WebApplicationFactory的连接字符串?
解决.NET API集成测试中替换MongoDB连接字符串的问题
问题背景
我正在对连接MongoDB的.NET API做集成测试,连接字符串与数据库名存储在appsettings.json中,使用xUnit和Testcontainers执行测试。原本想在测试间重置并重新填充测试库或执行回滚,但未找到简便方法。
现在尝试用Testcontainers创建本地测试数据库实例,可测试中构建并连接API时,它仍指向线上测试库而非本地实例。我希望每次测试时将appsettings.json的连接字符串替换为测试fixture生成的字符串,已了解自定义WebFactory和依赖注入,但不知如何结合现有代码实现。
原Program.cs代码
public partial class Program { static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); bool isTestEnvironment = Helper.IsTestEnvironment(); builder.Services.Configure<ArtsieDatabaseSettings>( builder.Configuration.GetSection(isTestEnvironment ? "ArtsieTestDatabase" : "ArtsieDatabase")); builder.Services.AddSingleton<ArtsieService>(); builder.Services.AddControllers() .AddJsonOptions( options => options.JsonSerializerOptions.PropertyNamingPolicy = null); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Art API", Description = "Browse some beautiful art", Version = "v1" }); }); var app = builder.Build(); app.UseHttpsRedirection(); app.MapControllers(); //... rest of swagger documentation and endpoints app.Run(); } }
原测试文件代码
namespace art_api.Tests; using ArtsieApi.Models; using Microsoft.AspNetCore.Mvc.Testing; using Newtonsoft.Json; using System.Net; using System.Text; using MongoDB.Driver; using Testcontainers.MongoDb; using Xunit; public class DatabaseFixture : IDisposable { private readonly IMongoDatabase _database; private readonly MongoDbContainer _container; public DatabaseFixture() { Environment.SetEnvironmentVariable("TEST_ENVIRONMENT", "true"); _container = new MongoDbBuilder().Build(); _container.StartAsync().Wait(); var connectionString = _container.GetConnectionString(); var client = new MongoClient(connectionString); _database = client.GetDatabase("artsie-test"); // Seed initial data SeedTestData(); } private void SeedTestData() { var artCollection = _database.GetCollection<List<Art>>("art"); var commentsCollection = _database.GetCollection<List<Comment>>("comments"); var usersCollection = _database.GetCollection<List<User>>("users"); } public void Dispose() { _database.DropCollection("art"); _database.DropCollection("comments"); _database.DropCollection("users"); _container.DisposeAsync().GetAwaiter().GetResult(); Environment.SetEnvironmentVariable("TEST_ENVIRONMENT", null); } } public class Endpoints : IClassFixture<DatabaseFixture> { private readonly DatabaseFixture _fixture; public Endpoints(DatabaseFixture fixture) { _fixture = fixture; } [Fact(DisplayName = "200: GET /")] public async Task TestRootEndpoint() { await using var application = new WebApplicationFactory<Program>(); using var client = application.CreateClient(); var response = await client.GetAsync("/"); var content = await response.Content.ReadAsStringAsync(); Assert.Equal("Hello World!", content); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } //... rest of tests }
解决方案步骤
1. 修改DatabaseFixture,暴露连接信息
将Testcontainers生成的连接字符串和数据库名暴露出来,供后续WebFactory调用:
public class DatabaseFixture : IDisposable { private readonly IMongoDatabase _database; private readonly MongoDbContainer _container; // 暴露连接字符串和数据库名 public string ConnectionString { get; } public string DatabaseName => "artsie-test"; public DatabaseFixture() { Environment.SetEnvironmentVariable("TEST_ENVIRONMENT", "true"); _container = new MongoDbBuilder().Build(); _container.StartAsync().Wait(); ConnectionString = _container.GetConnectionString(); var client = new MongoClient(ConnectionString); _database = client.GetDatabase(DatabaseName); SeedTestData(); } private void SeedTestData() { // 修正集合获取方式:单个实体而非List var artCollection = _database.GetCollection<Art>("art"); var commentsCollection = _database.GetCollection<Comment>("comments"); var usersCollection = _database.GetCollection<User>("users"); // 示例:填充测试数据 // artCollection.InsertMany(new List<Art> { new Art { Id = "1", Name = "测试艺术品" } }); } public void Dispose() { _database.DropCollection("art"); _database.DropCollection("comments"); _database.DropCollection("users"); _container.DisposeAsync().GetAwaiter().GetResult(); Environment.SetEnvironmentVariable("TEST_ENVIRONMENT", null); } }
2. 创建自定义WebApplicationFactory
继承WebApplicationFactory<Program>,重写配置逻辑,用Testcontainers的连接信息覆盖appsettings中的配置:
public class CustomWebApplicationFactory : WebApplicationFactory<Program> { private readonly string _mongoConnectionString; private readonly string _mongoDatabaseName; // 通过构造函数接收Fixture的连接信息 public CustomWebApplicationFactory(string connectionString, string databaseName) { _mongoConnectionString = connectionString; _mongoDatabaseName = databaseName; } protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureAppConfiguration(config => { // 添加内存配置,覆盖测试环境的数据库设置 var testConfig = new Dictionary<string, string> { {"ArtsieTestDatabase:ConnectionString", _mongoConnectionString}, {"ArtsieTestDatabase:DatabaseName", _mongoDatabaseName} }; config.AddInMemoryCollection(testConfig); }); // 可选:如果需要替换服务,可在此处修改DI容器 // builder.ConfigureServices(services => // { // var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(ArtsieService)); // if (descriptor != null) services.Remove(descriptor); // services.AddSingleton<ArtsieService>(); // }); } }
3. 修改测试类,使用自定义WebFactory
在测试方法中,用自定义工厂替代默认工厂,传入Fixture的连接信息:
public class Endpoints : IClassFixture<DatabaseFixture> { private readonly DatabaseFixture _fixture; public Endpoints(DatabaseFixture fixture) { _fixture = fixture; } [Fact(DisplayName = "200: GET /")] public async Task TestRootEndpoint() { // 使用自定义工厂,传入Testcontainers的连接信息 await using var application = new CustomWebApplicationFactory(_fixture.ConnectionString, _fixture.DatabaseName); using var client = application.CreateClient(); var response = await client.GetAsync("/"); var content = await response.Content.ReadAsStringAsync(); Assert.Equal("Hello World!", content); Assert.Equal(HttpStatusCode.OK, response.StatusCode); } // 其他测试方法统一使用该自定义工厂创建客户端 }
4. 验证配置实体类
确保ArtsieDatabaseSettings类包含必要的属性,与配置项对应:
public class ArtsieDatabaseSettings { public string ConnectionString { get; set; } = string.Empty; public string DatabaseName { get; set; } = string.Empty; // 其他数据库相关属性... }
修改完成后,测试时API会自动连接到Testcontainers创建的本地MongoDB实例,每次测试都会启动全新容器、填充测试数据,测试结束后销毁容器,保证测试的隔离性。
内容的提问来源于stack exchange,提问作者noepse
相关产品推荐
相关产品推荐

