Skip to content

Commit 71946ad

Browse files
authored
Refactor SNSTemplate tests to use LocalstackContainerTest (#1580)
* Refactor SNSTemplate tests to use LocalstackContainerTest Extract shared LocalStack container setup into LocalstackContainerTest interface, following the same pattern used in the S3 and DynamoDB modules. Fixes gh-1579 * Address review feedback: remove debug and use @container annotation
1 parent 05daa45 commit 71946ad

File tree

4 files changed

+94
-70
lines changed

4 files changed

+94
-70
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.awspring.cloud.sns;
17+
18+
import org.testcontainers.junit.jupiter.Container;
19+
import org.testcontainers.junit.jupiter.Testcontainers;
20+
import org.testcontainers.localstack.LocalStackContainer;
21+
import org.testcontainers.utility.DockerImageName;
22+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
23+
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
24+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
25+
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
26+
import software.amazon.awssdk.regions.Region;
27+
import software.amazon.awssdk.services.sns.SnsClient;
28+
import software.amazon.awssdk.services.sqs.SqsClient;
29+
30+
/**
31+
* The base contract for JUnit tests based on the container for Localstack. The Testcontainers 'reuse' option must be
32+
* disabled, so Ryuk container is started and will clean all the containers up from this test suite after JVM exit.
33+
* Since the Localstack container instance is shared via static property, it is going to be started only once per JVM;
34+
* therefore, the target Docker container is reused automatically.
35+
*
36+
* @author haroya01
37+
*
38+
* @since 4.0
39+
*/
40+
@Testcontainers(disabledWithoutDocker = true)
41+
public interface LocalstackContainerTest {
42+
43+
@Container
44+
LocalStackContainer LOCAL_STACK_CONTAINER = new LocalStackContainer(
45+
DockerImageName.parse("localstack/localstack:4.4.0"));
46+
47+
static SnsClient snsClient() {
48+
return applyAwsClientOptions(SnsClient.builder());
49+
}
50+
51+
static SqsClient sqsClient() {
52+
return applyAwsClientOptions(SqsClient.builder());
53+
}
54+
55+
static AwsCredentialsProvider credentialsProvider() {
56+
return StaticCredentialsProvider.create(
57+
AwsBasicCredentials.create(LOCAL_STACK_CONTAINER.getAccessKey(), LOCAL_STACK_CONTAINER.getSecretKey()));
58+
}
59+
60+
private static <B extends AwsClientBuilder<B, T>, T> T applyAwsClientOptions(B clientBuilder) {
61+
return clientBuilder.region(Region.of(LOCAL_STACK_CONTAINER.getRegion()))
62+
.credentialsProvider(credentialsProvider())
63+
.endpointOverride(LOCAL_STACK_CONTAINER.getEndpoint())
64+
.build();
65+
}
66+
67+
}

spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/core/batch/SnsBatchTemplateIntegrationTest.java

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package io.awspring.cloud.sns.core.batch;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.awaitility.Awaitility.await;
20+
21+
import io.awspring.cloud.sns.LocalstackContainerTest;
1822
import io.awspring.cloud.sns.Person;
1923
import io.awspring.cloud.sns.core.CachingTopicArnResolver;
2024
import io.awspring.cloud.sns.core.DefaultTopicArnResolver;
@@ -23,20 +27,19 @@
2327
import io.awspring.cloud.sns.core.TopicArnResolver;
2428
import io.awspring.cloud.sns.core.batch.converter.DefaultSnsMessageConverter;
2529
import io.awspring.cloud.sns.core.batch.executor.SequentialBatchExecutionStrategy;
30+
import java.time.Duration;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
import java.util.Map;
34+
import java.util.Set;
35+
import java.util.concurrent.atomic.AtomicInteger;
2636
import org.junit.jupiter.api.AfterEach;
2737
import org.junit.jupiter.api.BeforeAll;
2838
import org.junit.jupiter.api.Nested;
2939
import org.junit.jupiter.api.Test;
3040
import org.springframework.messaging.Message;
3141
import org.springframework.messaging.converter.JacksonJsonMessageConverter;
3242
import org.springframework.messaging.support.MessageBuilder;
33-
import org.testcontainers.junit.jupiter.Container;
34-
import org.testcontainers.junit.jupiter.Testcontainers;
35-
import org.testcontainers.localstack.LocalStackContainer;
36-
import org.testcontainers.utility.DockerImageName;
37-
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
38-
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
39-
import software.amazon.awssdk.regions.Region;
4043
import software.amazon.awssdk.services.sns.SnsClient;
4144
import software.amazon.awssdk.services.sns.model.CreateTopicRequest;
4245
import software.amazon.awssdk.services.sqs.SqsClient;
@@ -47,32 +50,19 @@
4750
import tools.jackson.databind.JsonNode;
4851
import tools.jackson.databind.json.JsonMapper;
4952

50-
import java.time.Duration;
51-
import java.util.ArrayList;
52-
import java.util.List;
53-
import java.util.Map;
54-
import java.util.Set;
55-
import java.util.concurrent.atomic.AtomicInteger;
56-
57-
import static org.assertj.core.api.Assertions.assertThat;
58-
import static org.awaitility.Awaitility.await;
59-
6053
/**
6154
* Integration tests for {@link SnsBatchTemplate}
6255
*
6356
* @author Matej Nedic
57+
* @author haroya01
6458
*/
65-
@Testcontainers
66-
class SnsBatchTemplateIntegrationTest {
59+
class SnsBatchTemplateIntegrationTest implements LocalstackContainerTest {
6760

6861
public static final String BATCH_TEST_TOPIC = "batch-test-topic";
6962
public static final String BATCH_TEST_QUEUE = "batch-test-queue";
7063
public static final String BATCH_TEST_TOPIC_FIFO = "batch-test-topic.fifo";
7164
public static final String BATCH_TEST_QUEUE_FIFO = "batch-test-queue.fifo";
7265
private static final JsonMapper jsonMapper = JsonMapper.builder().build();
73-
@Container
74-
static LocalStackContainer localstack = new LocalStackContainer(
75-
DockerImageName.parse("localstack/localstack:4.4.0"));
7666
private static SqsClient sqsClient;
7767
private static SnsBatchTemplate snsBatchTemplate;
7868
private static String standardTopicArn;
@@ -82,14 +72,8 @@ class SnsBatchTemplateIntegrationTest {
8272

8373
@BeforeAll
8474
static void setUp() {
85-
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider
86-
.create(AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey()));
87-
88-
SnsClient snsClient = SnsClient.builder().endpointOverride(localstack.getEndpoint())
89-
.credentialsProvider(credentialsProvider).region(Region.of(localstack.getRegion())).build();
90-
91-
sqsClient = SqsClient.builder().endpointOverride(localstack.getEndpoint())
92-
.credentialsProvider(credentialsProvider).region(Region.of(localstack.getRegion())).build();
75+
SnsClient snsClient = LocalstackContainerTest.snsClient();
76+
sqsClient = LocalstackContainerTest.sqsClient();
9377

9478
// Standard queue and Topic
9579
standardTopicArn = snsClient.createTopic(r -> r.name(BATCH_TEST_TOPIC)).topicArn();

spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/integration/SnsTemplateIntegrationTest.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.assertj.core.api.Assertions.*;
2121
import static org.awaitility.Awaitility.await;
2222

23+
import io.awspring.cloud.sns.LocalstackContainerTest;
2324
import io.awspring.cloud.sns.Person;
2425
import io.awspring.cloud.sns.core.SnsTemplate;
2526
import io.awspring.cloud.sns.core.TopicNotFoundException;
@@ -32,15 +33,8 @@
3233
import org.junit.jupiter.api.Nested;
3334
import org.junit.jupiter.api.Test;
3435
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
35-
import org.testcontainers.junit.jupiter.Container;
36-
import org.testcontainers.junit.jupiter.Testcontainers;
37-
import org.testcontainers.localstack.LocalStackContainer;
3836
import org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonNode;
3937
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
40-
import org.testcontainers.utility.DockerImageName;
41-
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
42-
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
43-
import software.amazon.awssdk.regions.Region;
4438
import software.amazon.awssdk.services.sns.SnsClient;
4539
import software.amazon.awssdk.services.sns.model.CreateTopicRequest;
4640
import software.amazon.awssdk.services.sqs.SqsClient;
@@ -53,29 +47,20 @@
5347
*
5448
* @author Matej Nedic
5549
* @author Hardik Singh Behl
50+
* @author haroya01
5651
*/
57-
@Testcontainers
58-
class SnsTemplateIntegrationTest {
52+
class SnsTemplateIntegrationTest implements LocalstackContainerTest {
53+
5954
private static final String TOPIC_NAME = "my_topic_name";
6055
private static SnsTemplate snsTemplate;
6156
private static SnsClient snsClient;
6257
private static final ObjectMapper objectMapper = new ObjectMapper();
6358
private static SqsClient sqsClient;
6459

65-
@Container
66-
static LocalStackContainer localstack = new LocalStackContainer(
67-
DockerImageName.parse("localstack/localstack:4.4.0"));
68-
6960
@BeforeAll
7061
public static void createSnsTemplate() {
71-
snsClient = SnsClient.builder().endpointOverride(localstack.getEndpoint())
72-
.region(Region.of(localstack.getRegion()))
73-
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("noop", "noop")))
74-
.build();
75-
sqsClient = SqsClient.builder().endpointOverride(localstack.getEndpoint())
76-
.region(Region.of(localstack.getRegion()))
77-
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("noop", "noop")))
78-
.build();
62+
snsClient = LocalstackContainerTest.snsClient();
63+
sqsClient = LocalstackContainerTest.sqsClient();
7964
MappingJackson2MessageConverter mappingJackson2MessageConverter = new MappingJackson2MessageConverter();
8065
mappingJackson2MessageConverter.setSerializedPayloadClass(String.class);
8166
snsTemplate = new SnsTemplate(snsClient, mappingJackson2MessageConverter);

spring-cloud-aws-sns/src/test/java/io/awspring/cloud/sns/sms/SnsSmsTemplateIntegrationTest.java

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,38 +18,26 @@
1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.awaitility.Awaitility.await;
2020

21+
import io.awspring.cloud.sns.LocalstackContainerTest;
2122
import org.junit.jupiter.api.Assertions;
2223
import org.junit.jupiter.api.BeforeAll;
2324
import org.junit.jupiter.api.Test;
2425
import org.testcontainers.containers.output.OutputFrame;
25-
import org.testcontainers.junit.jupiter.Container;
26-
import org.testcontainers.junit.jupiter.Testcontainers;
27-
import org.testcontainers.localstack.LocalStackContainer;
28-
import org.testcontainers.utility.DockerImageName;
29-
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
30-
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
31-
import software.amazon.awssdk.regions.Region;
3226
import software.amazon.awssdk.services.sns.SnsClient;
3327

3428
/**
3529
* Integration tests for {@link SnsSmsTemplate}.
3630
*
3731
* @author Matej Nedic
32+
* @author haroya01
3833
*/
39-
@Testcontainers
40-
class SnsSmsTemplateIntegrationTest {
41-
private static SnsSmsTemplate snsSmsTemplate;
34+
class SnsSmsTemplateIntegrationTest implements LocalstackContainerTest {
4235

43-
@Container
44-
static LocalStackContainer localstack = new LocalStackContainer(
45-
DockerImageName.parse("localstack/localstack:4.4.0")).withEnv("DEBUG", "1");
36+
private static SnsSmsTemplate snsSmsTemplate;
4637

4738
@BeforeAll
4839
public static void createSnsTemplate() {
49-
SnsClient snsClient = SnsClient.builder().endpointOverride(localstack.getEndpoint())
50-
.region(Region.of(localstack.getRegion()))
51-
.credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create("noop", "noop")))
52-
.build();
40+
SnsClient snsClient = LocalstackContainerTest.snsClient();
5341
snsSmsTemplate = new SnsSmsTemplate(snsClient);
5442
}
5543

@@ -58,7 +46,7 @@ void sendValidMessage_ToPhoneNumber() {
5846
Assertions.assertDoesNotThrow(() -> snsSmsTemplate.send("+385000000000", "Spring Cloud AWS got you covered!"));
5947

6048
await().untilAsserted(() -> {
61-
String logs = localstack.getLogs(OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR);
49+
String logs = LOCAL_STACK_CONTAINER.getLogs(OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR);
6250
assertThat(logs).contains("Delivering SMS message to +385000000000: Spring Cloud AWS got you covered!");
6351
});
6452
}
@@ -70,7 +58,7 @@ void sendValidMessage_ToPhoneNumber_WithAttributes() {
7058
.builder().smsType(SmsType.PROMOTIONAL).senderID("AWSPRING").maxPrice("1.00").build()));
7159

7260
await().untilAsserted(() -> {
73-
String logs = localstack.getLogs(OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR);
61+
String logs = LOCAL_STACK_CONTAINER.getLogs(OutputFrame.OutputType.STDOUT, OutputFrame.OutputType.STDERR);
7462
assertThat(logs).contains("Delivering SMS message to +385000000000: Spring Cloud AWS got you covered!");
7563
});
7664
}

0 commit comments

Comments
 (0)