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

Spring Boot集成测试:Testcontainers启动Auth服务容器失败求助

问题描述

在Spring Boot应用中用Testcontainers编写集成测试,测试用户服务(创建用户时会调用认证服务加密密码),启动两个容器:

  • postgres:17数据库容器:启动正常,可执行init.sql
  • 自定义构建的auth-service:latest容器:启动失败,报错如下:
Wait strategy failed. Container exited with code 1
Caused by: java.net.ConnectException: Connection refusedWaiting for URL: http://localhost:56873/auth/health
...
Timed out waiting for URL to be accessible (http://localhost:56873/auth/health should return HTTP [200])

测试代码:

@SpringBootTest(
        properties = {
                "logging.level.org.springframework.security=DEBUG",
        },
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@TestPropertySource(properties = {
        "spring.jpa.hibernate.ddl-auto=create-drop",
})
class UserControllerIntegrationTests {

    static Network network = Network.newNetwork();

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17")
            .withDatabaseName("testdb")
            .withUsername("testuser")
            .withPassword("testpass")
            .withNetwork(network)
            .withNetworkAliases("postgres-db");


    @Container
    static GenericContainer<?> authService = new GenericContainer<>("auth-service:latest")
            .withExposedPorts(8080)
            .withNetwork(network)
            .withNetworkAliases("auth-service")
            .waitingFor(Wait.forHttp("/auth/health")
                    .forStatusCode(200)
                    .withStartupTimeout(Duration.ofSeconds(20)));

    static {
        String initScriptPath = "./init.sql";
        try {
            MountableFile.forClasspathResource(initScriptPath);
            System.out.println("✅ Testcontainers: Found init script on classpath: " + initScriptPath);
            postgres.withInitScript(initScriptPath);
        } catch (IllegalArgumentException e) {
            System.err.println("❌ Testcontainers ERROR: Init script NOT found on classpath: " + initScriptPath);
            throw new RuntimeException("Failed to locate database initialization script.", e);
        }
    }

    @Autowired
    private TestRestTemplate restTemplate;

    @Value("${server.api.key}")
    private String apiKey;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private TestHelper httpTestHelper;

    @Autowired
    private JwtService jwtService;

    @Autowired
    private JwtAuthFilter jwtAuthFilter;

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
        registry.add("api.services.auth-service", () ->
                "http://" + authService.getHost() + ":" + authService.getMappedPort(8080));
    }
}
排查与解决方案

1. 查看auth-service容器启动日志

容器退出码1说明服务自身启动失败,先定位具体原因:

  • 在测试代码中添加日志输出:
    authService.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("auth-service")));
    
  • 或者手动启动容器查看日志:
    docker run -p 8080:8080 auth-service:latest
    

常见原因:服务依赖的数据库配置错误、环境变量缺失、端口冲突、代码内部异常等。

2. 修正健康检查的访问方式

如果auth-service的健康接口仅绑定容器内的localhost,宿主机无法直接访问。可以修改等待策略,改为在容器内部检测,或者调整服务的监听地址:

  • 方案一:使用日志匹配等待服务启动
    .waitingFor(Wait.forLogMessage(".*Started AuthServiceApplication.*", 1)
            .withStartupTimeout(Duration.ofSeconds(60)))
    
  • 方案二:让Testcontainers在容器内部发起健康检查请求
    .waitingFor(Wait.forHttp("/auth/health")
            .forStatusCode(200)
            .withStartupTimeout(Duration.ofSeconds(60))
            .insideContainer()) // 添加这行,使用容器内部网络访问
    

3. 确保auth-service与postgres的网络连通

如果auth-service需要连接postgres,必须使用容器网络别名postgres-db而非localhost:

  • 给auth-service传递正确的数据库环境变量:
    static GenericContainer<?> authService = new GenericContainer<>("auth-service:latest")
            .withExposedPorts(8080)
            .withNetwork(network)
            .withNetworkAliases("auth-service")
            .withEnv("SPRING_DATASOURCE_URL", "jdbc:postgresql://postgres-db:5432/testdb")
            .withEnv("SPRING_DATASOURCE_USERNAME", "testuser")
            .withEnv("SPRING_DATASOURCE_PASSWORD", "testpass")
            .waitingFor(...);
    

4. 指定容器启动依赖

默认Testcontainers并行启动容器,若auth-service依赖postgres,需明确依赖关系确保postgres先启动完成:

@Container
static GenericContainer<?> authService = new GenericContainer<>("auth-service:latest")
        .dependsOn(postgres) // 添加依赖
        .withExposedPorts(8080)
        .withNetwork(network)
        .withNetworkAliases("auth-service")
        .waitingFor(...);

5. 延长启动超时时间

如果auth-service启动较慢,20秒超时不够,延长至60秒或更久:

.waitingFor(Wait.forHttp("/auth/health")
        .forStatusCode(200)
        .withStartupTimeout(Duration.ofSeconds(60)))

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 21:34:56