You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:57:33