基于Testcontainers与LocalStack的AuthService集成测试配置求助
如何为AWS Cognito同步用户的AuthService编写正确的集成测试?
我正在尝试为将新用户同步到AWS Cognito用户池的AuthService编写集成测试,目前测试配置无法正常运行,需要调整配置以完成测试。以下是相关代码和配置:
业务代码(AuthService.java)
@Service @RequiredArgsConstructor public class AuthService { /** * The algorithm used for HMAC-SHA256 hashing. */ private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; /** * The client for interacting with Amazon Cognito Identity Provider. */ private final CognitoIdentityProviderClient client; /** * The AWS properties containing necessary configuration. */ private final AwsProperties properties; /** * Signs up a user using the provided SignUpDto. * * @param requestDto The SignUpDto containing user signup information. */ public void signUp(SignUpDto requestDto) { final AttributeType attributeType = AttributeType.builder() .name("email") .value(requestDto.email()) .build(); final String secretVal = calculateSecretHash(properties.getClientId(), properties.getSecretKey(), requestDto.email()); final SignUpRequest request = SignUpRequest.builder() .userAttributes(List.of(attributeType)) .username(requestDto.email()) .password(requestDto.password()) .clientId(properties.getClientId()) .secretHash(secretVal) .build(); client.signUp(request); } /** * Confirms user signup using the provided UserConfirmationDto. * * @param confirmation The UserConfirmationDto containing user confirmation information. */ public void confirmSignUp(UserConfirmationDto confirmation) { final ConfirmSignUpRequest req; req = ConfirmSignUpRequest.builder() .clientId(properties.getClientId()) .confirmationCode(confirmation.confirmationCode()) .username(confirmation.email()) .secretHash(calculateSecretHash(properties.getClientId(), properties.getSecretKey(), confirmation.email())) .build(); client.confirmSignUp(req); } /** * Calculates the secret hash for the provided user pool client ID, client secret, and email. * * @param userPoolClientId The user pool client ID. * @param userPoolClientSecret The user pool client secret. * @param email The user's email. * @return The calculated secret hash. * @throws SecretHashException If an error occurs during secret hash calculation. */ private String calculateSecretHash(String userPoolClientId, String userPoolClientSecret, String email) { final SecretKeySpec signingKey = new SecretKeySpec( userPoolClientSecret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM); final Mac mac; try { mac = Mac.getInstance(HMAC_SHA256_ALGORITHM); mac.init(signingKey); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new SecretHashException(e.getMessage()); } mac.update(email.getBytes(StandardCharsets.UTF_8)); final byte[] rawHmac = mac.doFinal(userPoolClientId.getBytes(StandardCharsets.UTF_8)); return java.util.Base64.getEncoder().encodeToString(rawHmac); } }
build.gradle配置
plugins { id 'java' id 'checkstyle' alias(libs.plugins.spring.boot) alias(libs.plugins.dependency.management) } group = 'java.com.mykytaaa' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(21) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation(libs.spring.starter.weblux) compileOnly(libs.lombok) annotationProcessor(libs.lombok) testImplementation(libs.spring.starter.test) testImplementation(libs.reactor) implementation(libs.mapstruct) annotationProcessor(libs.mapstruct.processor) testImplementation 'org.testcontainers:localstack:1.19.7' testImplementation 'org.testcontainers:junit-jupiter:1.19.7' testCompileOnly 'org.projectlombok:lombok:1.18.30' testAnnotationProcessor 'org.projectlombok:lombok:1.18.30' runtimeOnly(libs.awssdk.bom) implementation(libs.awssdk.cognitoidentityprovider) } tasks.named('test') { useJUnitPlatform() }
当前测试代码
AbstractIntegrationTestBase.java
@SpringBootTest @ActiveProfiles("test") public abstract class AbstractIntegrationTestBase { public static LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse("localstack/localstack")); @BeforeAll static void runContainer() { localStack.start(); } }
AwsConfiguration.java
@Configuration public class AwsConfiguration { /** * Creates and configures an instance of CognitoIdentityProviderClient. * * @param region The AWS region used for configuration. * @return An instance of CognitoIdentityProviderClient. */ @Bean public CognitoIdentityProviderClient identityProviderClient(@Value("${aws.region}") Region region) { return CognitoIdentityProviderClient.builder() .endpointOverride(AbstractIntegrationTestBase.localStack.getEndpoint()) .region(region) .build(); } }
AuthServiceIntegrationTest.java
@RequiredArgsConstructor @ExtendWith(SoftAssertionsExtension.class) public class AuthServiceIntegrationTest extends AbstractIntegrationTestBase { private final AuthService authService; @Test void shouldSuccessfullySignUpUser(SoftAssertions softly) { final SignUpDto signUpDto = new SignUpDto( "exampleemail@gmail.com", "qQ-123456789" ); authService.signUp(signUpDto); } }
调整方案
1. 修正LocalStack容器配置
使用Testcontainers官方注解管理容器生命周期,指定启用Cognito服务并设置必要环境变量:
@Testcontainers @SpringBootTest @ActiveProfiles("test") public abstract class AbstractIntegrationTestBase { @Container public static final LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:1.19.7")) .withServices(LocalStackContainer.Service.COGNITO_IDP) .withEnv("AWS_DEFAULT_REGION", "us-east-1") .withEnv("AWS_ACCESS_KEY_ID", "test") .withEnv("AWS_SECRET_ACCESS_KEY", "test"); }
- 用
@Testcontainers和@Container自动管理容器启停,无需手动调用start() - 指定
COGNITO_IDP服务,避免启动所有LocalStack服务 - 设置测试专用的AWS凭证和区域,符合LocalStack要求
2. 调整AWS客户端配置
移除对测试基类的硬依赖,使用容器提供的endpoint和测试凭证:
@Configuration public class AwsConfiguration { @Bean public CognitoIdentityProviderClient identityProviderClient(LocalStackContainer localStack) { AwsCredentials credentials = AwsBasicCredentials.create("test", "test"); return CognitoIdentityProviderClient.builder() .endpointOverride(localStack.getEndpointOverride(LocalStackContainer.Service.COGNITO_IDP)) .credentialsProvider(StaticCredentialsProvider.create(credentials)) .region(Region.of(localStack.getEnvMap().get("AWS_DEFAULT_REGION"))) .build(); } }
- 注入
LocalStackContainer实例,动态获取Cognito专属endpoint(LocalStack各服务endpoint不同) - 使用静态测试凭证,避免依赖外部配置
- 从容器环境变量中获取区域,保持配置统一
3. 测试前初始化Cognito资源
LocalStack启动时无任何预配置资源,必须先创建用户池和客户端才能执行注册操作:
@RequiredArgsConstructor @ExtendWith(SoftAssertionsExtension.class) @SpringBootTest @ActiveProfiles("test") public class AuthServiceIntegrationTest extends AbstractIntegrationTestBase { private final AuthService authService; private final CognitoIdentityProviderClient cognitoClient; private final AwsProperties awsProperties; @BeforeAll void setupCognitoResources() { // 创建用户池 CreateUserPoolResponse userPoolResponse = cognitoClient.createUserPool(CreateUserPoolRequest.builder() .poolName("test-user-pool") .autoVerifiedAttributes(List.of("email")) .build()); String userPoolId = userPoolResponse.userPool().id(); // 创建带密钥的用户池客户端(业务代码需要计算secretHash) CreateUserPoolClientResponse clientResponse = cognitoClient.createUserPoolClient(CreateUserPoolClientRequest.builder() .userPoolId(userPoolId) .clientName("test-client") .generateSecret(true) .build()); // 更新测试用AWS配置 awsProperties.setClientId(clientResponse.userPoolClient().clientId()); awsProperties.setSecretKey(clientResponse.userPoolClient().clientSecret()); awsProperties.setUserPoolId(userPoolId); awsProperties.setRegion(Region.of(localStack.getEnvMap().get("AWS_DEFAULT_REGION"))); } @Test void shouldSuccessfullySignUpUser(SoftAssertions softly) { final SignUpDto signUpDto = new SignUpDto( "exampleemail@gmail.com", "qQ-123456789" ); // 执行注册 authService.signUp(signUpDto); // 验证用户是否成功创建 AdminGetUserResponse userResponse = cognitoClient.adminGetUser(AdminGetUserRequest.builder() .userPoolId(awsProperties.getUserPoolId()) .username(signUpDto.email()) .build()); softly.assertThat(userResponse.username()).isEqualTo(signUpDto.email()); softly.assertThat(userResponse.userAttributes()).anyMatch(attr -> attr.name().equals("email") && attr.value().equals(signUpDto.email())); } }
- 在
@BeforeAll中创建用户池和带密钥的客户端,匹配业务代码的secretHash计算需求 - 更新
AwsProperties配置,让AuthService使用测试环境资源 - 添加验证逻辑,确认用户成功注册到Cognito
4. 补充测试配置文件
在src/test/resources/application-test.yml中添加基础配置:
aws: region: us-east-1
5. 完善AwsProperties类
确保包含必要的配置字段:
@ConfigurationProperties(prefix = "aws") @Data public class AwsProperties { private String region; private String clientId; private String secretKey; private String userPoolId; // 新增字段,存储测试用户池ID }
并在主配置类中添加@EnableConfigurationProperties(AwsProperties.class)注解,启用配置绑定。
内容的提问来源于stack exchange,提问作者ZED
相关产品推荐
相关产品推荐

