Testcontainers

Use Testcontainers with LocalStack
Testcontainers logo

Overview

Testcontainers is a library that helps you to run your tests against real dependencies.

In this guide, you will learn how to use Testcontainers with LocalStack.

Covered Topics

Installing the Localstack module

dotnet add package Testcontainers --version 2.4.0
go get github.com/testcontainers/testcontainers-go/modules/localstack
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>localstack</artifactId>
    <version>1.17.6</version>
    <scope>test</scope>
</dependency>
testImplementation 'org.testcontainers:localstack:1.17.6'

Obtaining a LocalStack container

/* The current released version of Testcontainers for .NET does not include a
LocalStack module. However, developers who want to use Testcontainers with
LocalStack should not be discouraged, as the Testcontainers team has already
developed a LocalStack module that will be included in the next release. In the
meantime, developers can still use Testcontainers' generic builder to create a
LocalStack container. */

const string localStackImage = "localstack/localstack:1.4.0";

const ushort localStackPort = 4566

var localStackContainer = new ContainerBuilder()
    .WithImage(localStackImage)
    .WithPortBinding(localStackPort, true)
    .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request =>
        request.ForPath("/_localstack/health").ForPort(localStackPort)))
    .Build();

await localStackContainer.StartAsync()
    .ConfigureAwait(false);
container, err := localstack.StartContainer(ctx, localstack.NoopOverrideContainerRequest)
LocalStackContainer localstack = new LocalStackContainer("localstack/localstack:1.4.0")
    .withServices(LocalStackContainer.Service.S3);

Configuring the AWS client

var config = new AmazonS3Config();
config.ServiceURL = _localStackContainer.GetConnectionString();
using var client = new AmazonS3Client(config);
func s3Client(ctx context.Context, l *localstack.LocalStackContainer) (*s3.Client, error) {
    // the Testcontainers Docker provider is used to get the host of the Docker daemon
    provider, err := testcontainers.NewDockerProvider()
    if err != nil {
        return nil, err
    }

    host, err := provider.DaemonHost(ctx)
    if err != nil {
        return nil, err
    }

    mappedPort, err := l.MappedPort(ctx, nat.Port("4566/tcp"))
    if err != nil {
        return nil, err
    }

    customResolver := aws.EndpointResolverWithOptionsFunc(
        func(service, region string, opts ...interface{}) (aws.Endpoint, error) {
            return aws.Endpoint{
                PartitionID:   "aws",
                URL:           fmt.Sprintf("http://%s:%d", host, mappedPort.Int()),
                SigningRegion: region,
            }, nil
        })

    awsCfg, err := config.LoadDefaultConfig(context.TODO(),
        config.WithRegion(region),
        config.WithEndpointResolverWithOptions(customResolver),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accesskey, secretkey, token)),
    )
    if err != nil {
        return nil, err
    }

    client := s3.NewFromConfig(awsCfg, func(o *s3.Options) {
        o.UsePathStyle = true
    })

    return client, nil
}
S3Client s3 = S3Client.builder()
    .endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.S3))
    .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())))
    .region(Region.of(localstack.getRegion()))
    .build();

Special Setup for using RDS

Some services like RDS require additional setup so that the correct port is exposed and accessible for the tests. The reserved ports on LocalStack are between 4510-4559, depending on your use case you might need to expose several ports using witExposedPorts.

Check the pro-sample on how to use RDS with Testcontainers for Java.

The Testcontainer can be created like this:

/**
  * Start LocalStackContainer with exposed Ports. Those ports are used by services like RDS, where several databases can be started, running on different ports.
  * In this sample we only map 5 ports, however, depending on your use case you may need to map ports up to 4559
*/
@Rule
public LocalStackContainer localstack = new LocalStackContainer(localstackImage)
                                                    .withExposedPorts(4510, 4511, 4512, 4513, 4514) // TODO the port can have any value between 4510-4559, but LS starts from 4510
                                                    .withEnv("LOCALSTACK_API_KEY", api_key) // TODO add your API key here
                                                    .withServices(LocalStackContainer.EnabledService.named("rds"));

To find the exposed port which you can use to connect to the instance:

// identify the port localstack provides for the instance
int localstack_port = response.dbInstance().endpoint().port();

// get the port it was mapped to, e.g. the one we can reach from host/the test
int mapped_port = localstack.getMappedPort(localstack_port);