1
0
mirror of https://github.com/docker/docs.git synced 2026-10-25 06:09:46 +03:00
Files
docs/content/guides/testcontainers-java-aws-localstack.md
Brandon Hunt 97cee42f3c docs: collapse folder+index.md guides to flat single-file guides (#25504)
## Description

Follow-up to #25338 (the guides revamp). That PR collapsed most
multi-page guide series into single pages but left ~50 guides wrapped in
a `<name>/index.md` directory structure alongside guides that are
already flat `<name>.md` files — an inconsistent mix. This PR finishes
the flattening:

- Flattens 50 folder-wrapped guides to `content/guides/<slug>.md` via
`git mv` (history preserved). These keep the same URL, since Hugo
generates the same permalink for a leaf-bundle `dir/index.md` and a flat
`dir.md`.
- Moves the last 4 guides' local per-guide image bundles
(`genai-leveraging-rag`, `genai-video-bot`, `genai-claude-code-mcp`,
`java`) into the shared `content/guides/images/` folder with
slug-prefixed filenames, matching the convention already used by most
other guides (`databases-1.webp`, `kafka-1.webp`, etc.), and rewrites
the in-body image references.
- Normalizes `genai-claude-code-mcp`'s non-standard filename
(`claude-code-mcp-guide.md`) and capitalized `Images/` folder to match
convention.
- Promotes `content/guides/frameworks/laravel/index.md` to a top-level
`content/guides/laravel.md`, since it was the only guide under
`frameworks/` and every other language/framework guide is flat.
- Adds `aliases` entries for the two URLs that actually changed
(`/guides/frameworks/laravel/` and
`/guides/genai-claude-code-mcp/claude-code-mcp-guide/`); every other
rename preserves its existing URL.

Companion PR establishing this as the documented convention going
forward: #25503

## Validation

- `hugo --minify` build via `docker compose run` completes with zero
warnings/errors (3174 pages, 1524 aliases — unchanged from before this
PR).
- Verified both changed-URL guides render at their new path and the old
path 301s via the Hugo alias redirect.
- Verified every image reference in the 4 migrated guides resolves to an
existing file in `content/guides/images/`.
- Multi-agent docs review (link-checker, cross-reference-checker,
guide-conventions) found zero issues.

## Related issues or tickets

Discussed in the team's docs Slack channel following #25338.

## Reviews

- [ ] Technical review
2026-07-09 08:45:55 -07:00

451 lines
14 KiB
Markdown

---
title: Testing AWS service integrations using LocalStack
linkTitle: AWS LocalStack
description: Learn how to test Spring Cloud AWS applications using LocalStack and Testcontainers.
keywords: testcontainers, java, spring boot, testing, aws, localstack, s3, sqs, spring cloud aws
summary: |
Learn how to create a Spring Boot application with Spring Cloud AWS,
then test S3 and SQS integrations using Testcontainers and LocalStack.
aliases:
- /guides/testcontainers-java-aws-localstack/create-project/
- /guides/testcontainers-java-aws-localstack/run-tests/
- /guides/testcontainers-java-aws-localstack/write-tests/
params:
tags: [testing]
time: 25 minutes
---
<!-- Source: https://github.com/testcontainers/tc-guide-testing-aws-service-integrations-using-localstack -->
In this guide, you will learn how to:
- Create a Spring Boot application with Spring Cloud AWS integration
- Use AWS S3 and SQS services
- Test the application using Testcontainers and LocalStack
## Prerequisites
- Java 17+
- Maven or Gradle
- A Docker environment supported by Testcontainers
> [!NOTE]
> If you're new to Testcontainers, visit the
> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
> Testcontainers and the benefits of using it.
## Create the Spring Boot project
### Set up the project
Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
by selecting the **Testcontainers** starter. Spring Cloud AWS starters are not
available on Spring Initializr, so you need to add them manually.
Alternatively, clone the
[guide repository](https://github.com/testcontainers/tc-guide-testing-aws-service-integrations-using-localstack).
Add the Spring Cloud AWS BOM to your dependency management and add the S3, SQS
starters as dependencies. Testcontainers provides a
[LocalStack module](https://testcontainers.com/modules/localstack/) for testing
AWS service integrations. You also need
[Awaitility](http://www.awaitility.org/) for testing asynchronous SQS
processing.
The key dependencies in `pom.xml` are:
```xml
<properties>
<java.version>17</java.version>
<testcontainers.version>2.0.4</testcontainers.version>
<awspring.version>3.0.3</awspring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-s3</artifactId>
</dependency>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-starter-sqs</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-localstack</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.awspring.cloud</groupId>
<artifactId>spring-cloud-aws-dependencies</artifactId>
<version>${awspring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
```
### Create the configuration properties
To make the SQS queue and S3 bucket names configurable, create an
`ApplicationProperties` record:
```java
package com.testcontainers.demo;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app")
public record ApplicationProperties(String queue, String bucket) {}
```
Then add `@ConfigurationPropertiesScan` to the main application class so that
Spring automatically scans for `@ConfigurationProperties`-annotated classes and
registers them as beans:
```java
package com.testcontainers.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
### Implement StorageService for S3
Spring Cloud AWS provides higher-level abstractions like `S3Template` with
convenience methods for uploading and downloading files. Create a
`StorageService` class:
```java
package com.testcontainers.demo;
import io.awspring.cloud.s3.S3Template;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.stereotype.Service;
@Service
public class StorageService {
private final S3Template s3Template;
public StorageService(S3Template s3Template) {
this.s3Template = s3Template;
}
public void upload(String bucketName, String key, InputStream stream) {
this.s3Template.upload(bucketName, key, stream);
}
public InputStream download(String bucketName, String key)
throws IOException {
return this.s3Template.download(bucketName, key).getInputStream();
}
public String downloadAsString(String bucketName, String key)
throws IOException {
try (InputStream is = this.download(bucketName, key)) {
return new String(is.readAllBytes());
}
}
}
```
### Create the SQS message model
Create a `Message` record that represents the payload you send to the SQS
queue:
```java
package com.testcontainers.demo;
import java.util.UUID;
public record Message(UUID uuid, String content) {}
```
### Implement the message sender
Create `MessageSender`, which uses `SqsTemplate` to publish messages:
```java
package com.testcontainers.demo;
import io.awspring.cloud.sqs.operations.SqsTemplate;
import org.springframework.stereotype.Service;
@Service
public class MessageSender {
private final SqsTemplate sqsTemplate;
public MessageSender(SqsTemplate sqsTemplate) {
this.sqsTemplate = sqsTemplate;
}
public void publish(String queueName, Message message) {
sqsTemplate.send(to -> to.queue(queueName).payload(message));
}
}
```
### Implement the message listener
Create `MessageListener` with a handler method annotated with `@SqsListener`.
When a message arrives, the listener uploads the content to an S3 bucket using
the message UUID as the key:
```java
package com.testcontainers.demo;
import io.awspring.cloud.sqs.annotation.SqsListener;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import org.springframework.stereotype.Service;
@Service
public class MessageListener {
private final StorageService storageService;
private final ApplicationProperties properties;
public MessageListener(
StorageService storageService,
ApplicationProperties properties
) {
this.storageService = storageService;
this.properties = properties;
}
@SqsListener(queueNames = { "${app.queue}" })
public void handle(Message message) {
String bucketName = this.properties.bucket();
String key = message.uuid().toString();
ByteArrayInputStream is = new ByteArrayInputStream(
message.content().getBytes(StandardCharsets.UTF_8)
);
this.storageService.upload(bucketName, key, is);
}
}
```
The `${app.queue}` expression reads the queue name from application
configuration instead of hard-coding it.
## Write tests with Testcontainers
To test the application, you need a running LocalStack instance that emulates
the AWS S3 and SQS services. Testcontainers spins up LocalStack in a Docker
container and `@DynamicPropertySource` connects it to Spring Cloud AWS.
### Configure the test container
You can start a LocalStack container and configure the Spring Cloud AWS
properties to talk to it instead of actual AWS services. The properties you
need to set are:
```properties
spring.cloud.aws.s3.endpoint=http://localhost:4566
spring.cloud.aws.sqs.endpoint=http://localhost:4566
spring.cloud.aws.credentials.access-key=noop
spring.cloud.aws.credentials.secret-key=noop
spring.cloud.aws.region.static=us-east-1
```
For testing, use an ephemeral container that starts on a random available port
so that you can run multiple builds in CI in parallel without port conflicts.
### Write the test
Create `MessageListenerTest.java`:
```java
package com.testcontainers.demo;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
import java.io.IOException;
import java.time.Duration;
import java.util.UUID;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;
@SpringBootTest
@Testcontainers
class MessageListenerTest {
@Container
static LocalStackContainer localStack = new LocalStackContainer(
DockerImageName.parse("localstack/localstack:3.0")
);
static final String BUCKET_NAME = UUID.randomUUID().toString();
static final String QUEUE_NAME = UUID.randomUUID().toString();
@DynamicPropertySource
static void overrideProperties(DynamicPropertyRegistry registry) {
registry.add("app.bucket", () -> BUCKET_NAME);
registry.add("app.queue", () -> QUEUE_NAME);
registry.add(
"spring.cloud.aws.region.static",
() -> localStack.getRegion()
);
registry.add(
"spring.cloud.aws.credentials.access-key",
() -> localStack.getAccessKey()
);
registry.add(
"spring.cloud.aws.credentials.secret-key",
() -> localStack.getSecretKey()
);
registry.add(
"spring.cloud.aws.s3.endpoint",
() -> localStack.getEndpointOverride(S3).toString()
);
registry.add(
"spring.cloud.aws.sqs.endpoint",
() -> localStack.getEndpointOverride(SQS).toString()
);
}
@BeforeAll
static void beforeAll() throws IOException, InterruptedException {
localStack.execInContainer("awslocal", "s3", "mb", "s3://" + BUCKET_NAME);
localStack.execInContainer(
"awslocal",
"sqs",
"create-queue",
"--queue-name",
QUEUE_NAME
);
}
@Autowired
StorageService storageService;
@Autowired
MessageSender publisher;
@Autowired
ApplicationProperties properties;
@Test
void shouldHandleMessageSuccessfully() {
Message message = new Message(UUID.randomUUID(), "Hello World");
publisher.publish(properties.queue(), message);
await()
.pollInterval(Duration.ofSeconds(2))
.atMost(Duration.ofSeconds(10))
.ignoreExceptions()
.untilAsserted(() -> {
String msg = storageService.downloadAsString(
properties.bucket(),
message.uuid().toString()
);
assertThat(msg).isEqualTo("Hello World");
});
}
}
```
Here's what the test does:
- `@SpringBootTest` starts the full Spring application context.
- The Testcontainers JUnit 5 annotations `@Testcontainers` and `@Container`
manage the lifecycle of a `LocalStackContainer` instance.
- `@DynamicPropertySource` obtains the dynamic S3 and SQS endpoint URLs,
region, access key, and secret key from the container, and registers them as
Spring Cloud AWS configuration properties.
- `@BeforeAll` creates the required SQS queue and S3 bucket using the
`awslocal` CLI tool that comes pre-installed in the LocalStack Docker image.
The `localStack.execInContainer()` API runs commands inside the container.
- `shouldHandleMessageSuccessfully()` publishes a `Message` to the SQS queue.
The listener receives the message and stores its content in the S3 bucket
with the UUID as the key. Awaitility waits up to 10 seconds for the expected
content to appear in the bucket.
## Run tests and next steps
### Run the tests
```console
$ ./mvnw test
```
Or with Gradle:
```console
$ ./gradlew test
```
You should see the LocalStack Docker container start and the test pass. After
the tests finish, the container stops and is removed automatically.
### Summary
LocalStack lets you develop and test AWS-based applications locally.
The Testcontainers LocalStack module makes it straightforward to write
integration tests by using ephemeral LocalStack containers that start on random
ports with no external setup required.
To learn more about Testcontainers, visit the
[Testcontainers overview](https://testcontainers.com/getting-started/).
### Further reading
- [Testcontainers LocalStack module](https://java.testcontainers.org/modules/localstack/)
- [Getting started with Testcontainers for Java](https://java.testcontainers.org/quickstart/junit_5_quickstart/)
- [Spring Cloud AWS documentation](https://docs.awspring.io/spring-cloud-aws/docs/3.0.3/reference/html/index.html)