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

Spring Boot中LocalStack Testcontainer测试报错:Secrets Manager找不到密钥

问题描述

在Spring Boot项目中使用LocalStack进行AWS相关测试时,执行HomeController的任意测试用例均出现以下报错:

com.amazonaws.services.secretsmanager.model.ResourceNotFoundException: Secrets Manager can't find the specified secret. (Service: AWSSecretsManager; Status Code: 404; Error Code: ResourceNotFoundException; Request ID: null; Proxy: null)

怀疑是LocalStackContainer在执行相关操作前未完成启动,请问该如何解决?

相关代码

BaseIntegrationTest类

@ContextConfiguration(initializers = BaseIntegrationTest.Initializer.class)
public class BaseIntegrationTest {

public static final String LOCALSTACK_HOSTNAME = "localhost";
public static final String LOCALSTACK_REGION = "eu-west-3";
public static final String LOCALSTACK_ACCESS_KEY = "test";
public static final String LOCALSTACK_SECRET_KEY = "test";

private static LocalStackContainer localStackContainer = new LocalStackContainer()
        .withServices(LocalStackContainer.Service.S3)
        .withServices(LocalStackContainer.Service.S3)
        .withServices(LocalStackContainer.Service.SECRETSMANAGER)
        .withExposedPorts(4566, 4566)
        .withEnv("HOSTNAME_EXTERNAL", LOCALSTACK_HOSTNAME)
        .withEnv("DEFAULT_REGION", LOCALSTACK_REGION)
        .withEnv("AWS_ACCESS_KEY_ID", LOCALSTACK_ACCESS_KEY)
        .withEnv("AWS_SECRET_ACCESS_KEY", LOCALSTACK_SECRET_KEY);;

static {
    localStackContainer.start();
}

static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        String localStackHost;
        try {
            localStackHost = initializeLocalStack();
        } catch (IOException | InterruptedException e) {
        throw new RuntimeException(e);
        }

        TestPropertySourceUtils.addInlinedPropertiesToEnvironment(applicationContext,localStackHost);
    }
}

private static String initializeLocalStack() throws IOException, InterruptedException {

    localStackContainer.execInContainer("aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name aws/secret --secret-string '{\"my_uname\":\"username\",\"my_pwd\":\"password\"}'");
    localStackContainer.execInContainer("aws --endpoint-url=http://localhost:4566  s3api create-bucket --bucket bucketname --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-3");
    final Integer mappedPort = localStackContainer.getMappedPort(4566);
    return "cloud.aws.secrets-manager.end-point.uri=http://localhost:" + mappedPort;
}
}

BaseRestControllerTest类

@SpringBootTest
@AutoConfigureMockMvc
public abstract class BaseRestControllerTest extends BaseIntegrationTest {
}

HomeControllerTest类

class HomeControllerTest extends BaseRestControllerTest {

}
解决方案

你的怀疑是对的,LocalStack调用start()后不会立即完成所有服务初始化,此时执行创建secret的命令会因服务未就绪失败,导致测试时找不到目标secret。可以通过以下步骤修复:

  • 等待服务就绪后再执行初始化命令
    LocalStack提供waitUntilServiceIsReady方法,可指定等待特定服务完全启动。修改initializeLocalStack方法,在执行创建命令前先等待SecretsManager和S3就绪:

    private static String initializeLocalStack() throws IOException, InterruptedException {
        // 等待SecretsManager服务就绪
        localStackContainer.waitUntilServiceIsReady(LocalStackContainer.Service.SECRETSMANAGER);
        // 等待S3服务就绪
        localStackContainer.waitUntilServiceIsReady(LocalStackContainer.Service.S3);
        
        localStackContainer.execInContainer("aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name aws/secret --secret-string '{\"my_uname\":\"username\",\"my_pwd\":\"password\"}'");
        localStackContainer.execInContainer("aws --endpoint-url=http://localhost:4566  s3api create-bucket --bucket bucketname --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-3");
        final Integer mappedPort = localStackContainer.getMappedPort(4566);
        return "cloud.aws.secrets-manager.end-point.uri=http://localhost:" + mappedPort;
    }
    
  • 修正容器启动时机与配置冗余
    不要在静态代码块中直接启动容器,而是在初始化方法中判断容器状态后再启动;同时移除重复的withServices(S3)配置:

    private static LocalStackContainer localStackContainer = new LocalStackContainer()
            .withServices(LocalStackContainer.Service.S3, LocalStackContainer.Service.SECRETSMANAGER)
            .withEnv("HOSTNAME_EXTERNAL", LOCALSTACK_HOSTNAME)
            .withEnv("DEFAULT_REGION", LOCALSTACK_REGION)
            .withEnv("AWS_ACCESS_KEY_ID", LOCALSTACK_ACCESS_KEY)
            .withEnv("AWS_SECRET_ACCESS_KEY", LOCALSTACK_SECRET_KEY);
    

    调整初始化方法中的启动逻辑:

    private static String initializeLocalStack() throws IOException, InterruptedException {
        if (!localStackContainer.isRunning()) {
            localStackContainer.start();
        }
        localStackContainer.waitUntilServiceIsReady(LocalStackContainer.Service.SECRETSMANAGER);
        localStackContainer.waitUntilServiceIsReady(LocalStackContainer.Service.S3);
        
        // 后续创建命令不变
    }
    
  • 校验初始化命令执行结果
    执行execInContainer后,添加退出码校验,确保secret和bucket创建成功,避免静默失败:

    ExecResult secretResult = localStackContainer.execInContainer("aws --endpoint-url=http://localhost:4566 secretsmanager create-secret --name aws/secret --secret-string '{\"my_uname\":\"username\",\"my_pwd\":\"password\"}'");
    if (secretResult.getExitCode() != 0) {
        throw new RuntimeException("创建Secret失败: " + secretResult.getStderr());
    }
    
    ExecResult bucketResult = localStackContainer.execInContainer("aws --endpoint-url=http://localhost:4566  s3api create-bucket --bucket bucketname --region eu-west-1 --create-bucket-configuration LocationConstraint=eu-west-3");
    if (bucketResult.getExitCode() != 0) {
        throw new RuntimeException("创建Bucket失败: " + bucketResult.getStderr());
    }
    
  • 改用AWS SDK替代Shell命令
    避免直接执行shell命令,使用AWS SDK客户端与LocalStack交互,能更可靠地处理服务状态:

    // 创建SecretsManager客户端
    AWSSecretsManager secretsManager = AWSSecretsManagerClientBuilder.standard()
            .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(
                    localStackContainer.getEndpointOverride(LocalStackContainer.Service.SECRETSMANAGER).toString(),
                    LOCALSTACK_REGION
            ))
            .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(LOCALSTACK_ACCESS_KEY, LOCALSTACK_SECRET_KEY)))
            .build();
    // 创建secret
    secretsManager.createSecret(new CreateSecretRequest()
            .withName("aws/secret")
            .withSecretString("{\"my_uname\":\"username\",\"my_pwd\":\"password\"}"));
    

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 19:45:39