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

Testcontainers集成测试问题:容器启动后退出及超时错误求助

问题描述

原本基于inMemoryDatabase & XUnit的集成测试,计划用testcontainers 3.5重构为Docker镜像运行,但遇到两个问题:

  1. System.TimeoutException: The operation has timed out,出现在XUnit的CreateClassFixtureAsync(Type fixtureType)方法中;
  2. Docker API responded with status code=Conflict, response={message:Container %some_id% is not running},原因是Docker容器启动后立即变为Exited状态。

相关代码如下:

Factory.cs

public class IntegrationTestApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{

    private const string ImageName = "ubuntu:16.04"; // tips #1
    private const string Database = "master";
    private const string Username = "sa";
    private const string Password = "myStr0ng!&Passw0rd!";
    private int MsSqlPort = Random.Shared.Next(10000, 60000);
    
    private readonly IContainer _mssqlContainer;
    
    public IntegrationTestMalumApplicationFactory()
    {
        _mssqlContainer = new ContainerBuilder()
            .WithImage(ImageName)
            .WithName(Guid.NewGuid().ToString())
            .WithEnvironment("ACCEPT_EULA", "Y")
            .WithEnvironment("SQLCMDUSER", Username)
            .WithEnvironment("SQLCMDPASSWORD", Password)
            .WithEnvironment("MSSQL_SA_PASSWORD", Password)
            .WithEnvironment("MSSQL_PID", "Evaluation")
            .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(MsSqlPort))
            .WithPortBinding(MsSqlPort, true)
            .WithExposedPort(MsSqlPort)
            .WithCleanUp(true)
            .Build();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        var host = _mssqlContainer.Hostname;
        var port = _mssqlContainer.GetMappedPublicPort(MsSqlPort);
        
        builder.ConfigureTestServices(services =>
        {
            services.RemoveAll(typeof(DbContextOptions<DataAccess.Context>));

            services.AddDbContextPool<DataAccess.Context>((options, optionsBuilder) =>
            {
                optionsBuilder.UseSqlServer(
                    $"Server={host},{port};Database={Database};User Id={Username};Password={Password};TrustServerCertificate=true");
            });
        });
    }

    public async Task InitializeAsync() => await _mssqlContainer.StartAsync();
    public async new Task DisposeAsync() => await _mssqlContainer.StopAsync();
}

Test.cs

public class IntegrationTestMalumApi : IClassFixture<IntegrationTestApplicationFactory>
{
    protected readonly HttpClient TestClient;
    protected readonly ApplicationUrl ApplicationUrl;

    public IntegrationTestMalumApi(IntegrationTestApplicationFactory applicationFactory)
    {
        TestClient = applicationFactory.CreateClient();
        ApplicationUrl = new ApplicationUrl("http://localhost:1324");
    }

    [Fact]
    public async Task GetCats_WithThreeCats_ReturnHttpCodeOK()
    {
        // to do something
    }
}

补充说明:

  • 尝试过mcr.microsoft.com/mssql/server:2022-preview-ubuntu-22.04镜像,但只有ubuntu:16.04能"运行";
  • ApplicationUrl是存储API路由的类;
  • 问题有时出现在XunitTestClassRunner.cs的CreateClassFixtureAsync,有时出现在InitializeAsync()调用_mssqlContainer.StartAsync()时;
  • 即使基础配置也无法正常运行。
问题根源与修正方案

核心错误:镜像选择完全错误

ubuntu:16.04是纯基础Linux镜像,没有预装MSSQL服务,容器启动后没有长期运行的进程,立刻就会退出(Exited状态),这是所有问题的根源。你之前尝试的MSSQL官方镜像才是正确选择,之前无法运行大概率是配置问题(比如密码策略、环境变量错误)。

其他关键错误点

  1. 端口配置错误:MSSQL容器默认监听内部1433端口,你自定义的随机MsSqlPort在容器内没有对应的服务,等待策略永远无法检测到端口可用,最终触发超时。
  2. 配置时机错误:ConfigureWebHost在容器启动前就会执行,此时调用GetMappedPublicPort会获取到无效的端口值,导致数据库连接字符串错误。
  3. DisposeAsync实现错误:用new修饰DisposeAsync会隐藏基类方法,可能导致容器无法正确停止和清理。

修正后的代码

Factory.cs

using Testcontainers.MsSql;

public class IntegrationTestApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
    private const string Database = "master";
    private const string Username = "sa";
    private const string Password = "MyStr0ng!&Passw0rd123"; // 密码需符合MSSQL复杂度要求:至少8位,包含大小写、数字、特殊字符
    private readonly MsSqlContainer _mssqlContainer;

    public IntegrationTestApplicationFactory()
    {
        // 使用官方MSSQL容器镜像,Testcontainers提供的MsSqlContainer封装了所有默认配置
        _mssqlContainer = new MsSqlBuilder()
            .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
            .WithPassword(Password)
            .WithCleanUp(true)
            .Build();
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureTestServices(services =>
        {
            services.RemoveAll(typeof(DbContextOptions<DataAccess.Context>));

            // 直接使用容器提供的连接字符串,无需手动拼接
            services.AddDbContextPool<DataAccess.Context>((options, optionsBuilder) =>
            {
                optionsBuilder.UseSqlServer(
                    _mssqlContainer.GetConnectionString(),
                    opt => opt.EnableRetryOnFailure());
            });
        });
    }

    public async Task InitializeAsync()
    {
        await _mssqlContainer.StartAsync();
        // 可选:如果需要初始化数据库结构,可在这里执行迁移脚本
        // await InitializeDatabase();
    }

    public async Task DisposeAsync()
    {
        await _mssqlContainer.StopAsync();
        await _mssqlContainer.DisposeAsync();
        await base.DisposeAsync();
    }

    // 可选:初始化数据库结构
    // private async Task InitializeDatabase()
    // {
    //     await using var scope = Services.CreateScope();
    //     var dbContext = scope.ServiceProvider.GetRequiredService<DataAccess.Context>();
    //     await dbContext.Database.MigrateAsync();
    // }
}

Test.cs(小调整)

public class IntegrationTestMalumApi : IClassFixture<IntegrationTestApplicationFactory>
{
    protected readonly HttpClient TestClient;
    protected readonly ApplicationUrl ApplicationUrl;

    public IntegrationTestMalumApi(IntegrationTestApplicationFactory applicationFactory)
    {
        TestClient = applicationFactory.CreateClient();
        // 使用TestClient的BaseAddress,无需硬编码端口
        ApplicationUrl = new ApplicationUrl(TestClient.BaseAddress.ToString());
    }

    [Fact]
    public async Task GetCats_WithThreeCats_ReturnHttpCodeOK()
    {
        // 测试逻辑示例
        var response = await TestClient.GetAsync(ApplicationUrl.CatsEndpoint);
        response.EnsureSuccessStatusCode();
        Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
    }
}

额外说明

  1. MSSQL密码要求:官方镜像要求SA密码必须符合复杂度规则(至少8字符,包含大小写字母、数字、特殊字符),之前你用的密码myStr0ng!&Passw0rd!虽然看起来复杂,但全小写开头可能不符合要求,修正后改成首字母大写并添加数字;
  2. Testcontainers封装类:使用MsSqlContainer而不是通用ContainerBuilder,可以省去手动配置环境变量、端口、等待策略等繁琐步骤,Testcontainers会自动处理;
  3. 连接字符串获取:GetConnectionString()会自动生成正确的连接字符串,包含映射后的主机端口、认证信息等,避免手动拼接出错。

内容的提问来源于stack exchange,提问作者tender

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 03:17:10