无法使用测试容器的 localstack 模块连接到 Aws SQS

Posted

技术标签:

【中文标题】无法使用测试容器的 localstack 模块连接到 Aws SQS【英文标题】:Unable to connect to Aws SQS using localstack module of test container 【发布时间】:2021-05-27 16:58:30 【问题描述】:

我正在尝试为集成测试设置环境,到目前为止,我已经使用了测试容器的 postgrsql 模块并且能够使其工作,但是 localstack 在我运行测试时抛出以下异常。

org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [com.hardik.killchamber.service.PersonService personService] in constructor [public com.hardik.killchamber.test.LocalstackSpringBootPocApplicationTests(com.hardik.killchamber.service.PersonService)]: Failed to load ApplicationContext
    at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameter(ExecutableInvoker.java:239)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameters(ExecutableInvoker.java:183)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:74)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeTestClassConstructor(ClassBasedTestDescriptor.java:342)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateTestClass(ClassBasedTestDescriptor.java:289)
    at org.junit.jupiter.engine.descriptor.ClassTestDescriptor.instantiateTestClass(ClassTestDescriptor.java:79)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:267)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
    at java.base/java.util.Optional.orElseGet(Optional.java:362)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
    at org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor.instantiateTestClass(NestedClassTestDescriptor.java:85)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.instantiateAndPostProcessTestInstance(ClassBasedTestDescriptor.java:267)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$2(ClassBasedTestDescriptor.java:259)
    at java.base/java.util.Optional.orElseGet(Optional.java:362)
    at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$testInstancesProvider$3(ClassBasedTestDescriptor.java:258)
    at org.junit.jupiter.engine.execution.TestInstancesProvider.getTestInstances(TestInstancesProvider.java:31)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$prepare$0(TestMethodTestDescriptor.java:101)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:100)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.prepare(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:84)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
    at org.springframework.test.context.junit.jupiter.SpringExtension.getApplicationContext(SpringExtension.java:283)
    at org.springframework.test.context.junit.jupiter.SpringExtension.resolveParameter(SpringExtension.java:269)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.resolveParameter(ExecutableInvoker.java:216)
    ... 69 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpleMessageListenerContainer' defined in class path resource [org/springframework/cloud/aws/messaging/config/annotation/SqsConfiguration.class]: Invocation of init method failed; nested exception is com.amazonaws.services.sqs.model.AmazonSQSException: The security token included in the request is invalid. (Service: AmazonSQS; Status Code: 403; Error Code: InvalidClientTokenId; Request ID: 609a8137-add4-5c69-a298-d986cc31ef35)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:582)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
    ... 73 more
Caused by: com.amazonaws.services.sqs.model.AmazonSQSException: The security token included in the request is invalid. (Service: AmazonSQS; Status Code: 403; Error Code: InvalidClientTokenId; Request ID: 609a8137-add4-5c69-a298-d986cc31ef35)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1660)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1324)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1074)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:745)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:719)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:701)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:669)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:651)
    at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:515)
    at com.amazonaws.services.sqs.AmazonSQSClient.doInvoke(AmazonSQSClient.java:2147)
    at com.amazonaws.services.sqs.AmazonSQSClient.invoke(AmazonSQSClient.java:2116)
    at com.amazonaws.services.sqs.AmazonSQSClient.invoke(AmazonSQSClient.java:2105)
    at com.amazonaws.services.sqs.AmazonSQSClient.executeGetQueueUrl(AmazonSQSClient.java:1138)
    at com.amazonaws.services.sqs.AmazonSQSClient.getQueueUrl(AmazonSQSClient.java:1110)
    at com.amazonaws.services.sqs.buffered.AmazonSQSBufferedAsyncClient.getQueueUrl(AmazonSQSBufferedAsyncClient.java:260)
    at org.springframework.cloud.aws.messaging.support.destination.DynamicQueueUrlDestinationResolver.resolveDestination(DynamicQueueUrlDestinationResolver.java:94)
    at org.springframework.cloud.aws.messaging.support.destination.DynamicQueueUrlDestinationResolver.resolveDestination(DynamicQueueUrlDestinationResolver.java:38)
    at org.springframework.messaging.core.CachingDestinationResolverProxy.resolveDestination(CachingDestinationResolverProxy.java:92)
    at org.springframework.cloud.aws.messaging.listener.AbstractMessageListenerContainer.queueAttributes(AbstractMessageListenerContainer.java:321)
    at org.springframework.cloud.aws.messaging.listener.AbstractMessageListenerContainer.initialize(AbstractMessageListenerContainer.java:293)
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer.initialize(SimpleMessageListenerContainer.java:111)
    at org.springframework.cloud.aws.messaging.listener.AbstractMessageListenerContainer.afterPropertiesSet(AbstractMessageListenerContainer.java:268)
    at org.springframework.cloud.aws.messaging.listener.SimpleMessageListenerContainer.afterPropertiesSet(SimpleMessageListenerContainer.java:45)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782)
    ... 89 more

这是 Localstack 容器代码,其中我有一个静态容器变量,我正在初始化和启动静态块中的容器,我在其中测试 aws 服务的所有测试类都将扩展以下类

@Testcontainers
public class AmazonWebServiceTestContainer 

    public final static LocalStackContainer container;

    public final static String bucketName;

    public final static String topicArn;

    public final static String queueName;
    
    private final static AmazonSNSClient amazonSnsClient;
    
    private final static AmazonSQSAsync amazonSQSAsync; 
    
    private final static AmazonS3 amazonS3;

    static 
        container = new LocalStackContainer(DockerImageName.parse("localstack/localstack:0.11.3")).withServices(S3, SNS,
                SQS);

        if (!(container.isCreated() && container.isRunning()))
            container.start();

        // MAKE S3 Bucket
        amazonS3 =  AmazonS3ClientBuilder.standard().withEndpointConfiguration(container.getEndpointConfiguration(S3))
                .withCredentials(container.getDefaultCredentialsProvider()).build();
        bucketName = amazonS3
                .createBucket(RandomString.make(8).toLowerCase()).getName();

        // INIT SNS CLIENT
        amazonSnsClient = (AmazonSNSClient)AmazonSNSClientBuilder.standard()
                .withEndpointConfiguration(container.getEndpointConfiguration(SNS))
                .withCredentials(container.getDefaultCredentialsProvider()).build();

        // CREATE SNS TOPIC
        topicArn = amazonSnsClient.createTopic(RandomString.make(7)).getTopicArn();

        // STATIC QUEUE NAME TO MATCH @SqsListener CODE
        queueName = "killchamber-queue";

        // INIT SQS CLIENT
        amazonSQSAsync = AmazonSQSAsyncClientBuilder.standard().withRegion(container.getRegion())
        .withCredentials(new AWSStaticCredentialsProvider(
                new BasicAWSCredentials(container.getAccessKey(), container.getSecretKey())))
        .build();
        
        amazonSQSAsync.createQueue(queueName);

        // SUBSCRIBE SQS TO SNS TOPIC
        //SubscribeRequest request = new SubscribeRequest(topicArn, "sqs", null);
        //amazonSnsClient.subscribe(request);

    

    @Primary
    @Bean
    @Profile("testing")
    public AmazonSNSClient getSnsClient() 
        return amazonSnsClient;
    

    @Primary
    @Bean
    @Profile("testing")
    public AmazonSQSAsync amazonSQSAsync() 
        return amazonSQSAsync;
    

    @Bean
    @Profile("testing")
    public AmazonS3 amazonS3() 
        return amazonS3;
    

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry registry) 
        registry.add("cloud.aws.region.static", container::getRegion);

        registry.add("com.hardik.killchamber.storage.access-key", container::getAccessKey);
        registry.add("com.hardik.killchamber.storage.secret-key", container::getSecretKey);
        registry.add("com.hardik.killchamber.storage.region", container::getRegion);
        registry.add("com.hardik.killchamber.storage.bucket-name", () -> bucketName);

        registry.add("com.hardik.killchamber.sns.properties.access-key", container::getAccessKey);
        registry.add("com.hardik.killchamber.sns.properties.secret-key", container::getSecretKey);
        registry.add("com.hardik.killchamber.sns.properties.topic-arn", () -> topicArn);

        registry.add("com.hardik.killchamber.sqs.properties.access-key", container::getAccessKey);
        registry.add("com.hardik.killchamber.sqs.properties.secret-key", container::getSecretKey);
        registry.add("com.hardik.killchamber.sqs.properties.queue-name", () -> queueName);
    



我现在只有测试类,因为它抛出了上述错误

@ExtendWith(SpringExtension.class)
@SpringBootTest
class LocalstackSpringBootPocApplicationTests extends PostgresqlTestContainer
    
    private final  PersonService personService;
    
    @Autowired
    public LocalstackSpringBootPocApplicationTests(PersonService personService) 
        super();
        this.personService = personService;
    

    @Nested
    public class Demo extends AmazonWebServiceTestContainer

        @Test
        void contextLoads() 
            MockMultipartFile file = new MockMultipartFile("abc.png", "HARDIK SINGH BEHL".getBytes());
            personService.create("\"email\":\"behlHardik@gmail.com\",\"fullName\":\"behlHardik\",\"age\":28", file);
        
    


这里是代码的github仓库 https://github.com/hardikSinghBehl/localstack-spring-boot

【问题讨论】:

【参考方案1】:

您的错误的根本原因是@SpringBootTest 看不到您在AmazonWebServiceTestContainer 类中定义的自定义spring bean(如amazonSQSAsync)并使用spring-cloud-aws 提供的那些。 所以本质上你的测试是在尝试使用真正的 AWS,而不是 localstack。

为了使 localstack-aware bean 可见,它们应该定义在一个用 @Configuration@TestConfiguration 注释的类中。将其中一个标记为AmazonWebServiceTestContainer 作为快速修复。 如果您更喜欢第二个注解 (@TestConfiguration),您还需要导入它:

@SpringBootTest
@Import(AmazonWebServiceTestContainer.class)
class LocalstackSpringBootPocApplicationTests extends PostgresqlTestContainer

更简洁的长团队解决方案是将 bean 提取到单独的类中,例如 LocalAwsConfiguration

【讨论】:

以上是关于无法使用测试容器的 localstack 模块连接到 Aws SQS的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 localstack dynamoDB 锁定 terraform 状态:UnrecognizedClientException

Localstack 抛出请求中包含的安全令牌无效

localstack 1.0 ga 了

由 Terraform 在 LocalStack 中创建的 DynamoDB 表在 NoSQL Workbench 中不可见

使用 Docker 容器后无法连接 Oracle 数据库

TestContainers 和错误:“无法验证连接 org.postgresql.jdbc.PgConnection”(为所有测试类提升单个容器)