Skip to content

Testcontainers — Go

The testcontainers-floci-go module integrates Floci with Testcontainers for Go. It starts a real Floci container before your tests and shuts it down after, with no extra setup.

Installation

go get github.com/floci-io/testcontainers-floci-go

Requires Go 1.25+ and Testcontainers for Go v0.42+.

Basic usage

package myservice_test

import (
    "context"
    "testing"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/credentials"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    floci "github.com/floci-io/testcontainers-floci-go"
)

func TestS3CreateBucket(t *testing.T) {
    ctx := context.Background()

    container, err := floci.NewFlociContainer().Start(ctx)
    if err != nil {
        t.Fatal(err)
    }
    defer container.Stop(ctx)

    cfg, err := config.LoadDefaultConfig(ctx,
        config.WithRegion(container.GetRegion()),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            container.GetAccessKey(), container.GetSecretKey(), "",
        )),
        config.WithBaseEndpoint(container.GetEndpoint()),
    )
    if err != nil {
        t.Fatal(err)
    }

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

    _, err = client.CreateBucket(ctx, &s3.CreateBucketInput{
        Bucket: aws.String("my-bucket"),
    })
    if err != nil {
        t.Fatal(err)
    }

    out, err := client.ListBuckets(ctx, &s3.ListBucketsInput{})
    if err != nil {
        t.Fatal(err)
    }

    var found bool
    for _, b := range out.Buckets {
        if aws.ToString(b.Name) == "my-bucket" {
            found = true
            break
        }
    }
    if !found {
        t.Error("bucket not found after create")
    }
}

SQS example

func TestSqsSendReceive(t *testing.T) {
    ctx := context.Background()

    container, err := floci.NewFlociContainer().Start(ctx)
    if err != nil {
        t.Fatal(err)
    }
    defer container.Stop(ctx)

    cfg, _ := config.LoadDefaultConfig(ctx,
        config.WithRegion(container.GetRegion()),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            container.GetAccessKey(), container.GetSecretKey(), "",
        )),
        config.WithBaseEndpoint(container.GetEndpoint()),
    )

    client := sqs.NewFromConfig(cfg)

    queue, err := client.CreateQueue(ctx, &sqs.CreateQueueInput{
        QueueName: aws.String("orders"),
    })
    if err != nil {
        t.Fatal(err)
    }

    _, err = client.SendMessage(ctx, &sqs.SendMessageInput{
        QueueUrl:    queue.QueueUrl,
        MessageBody: aws.String(`{"event":"order.placed"}`),
    })
    if err != nil {
        t.Fatal(err)
    }

    out, err := client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
        QueueUrl:            queue.QueueUrl,
        MaxNumberOfMessages: 1,
    })
    if err != nil {
        t.Fatal(err)
    }

    if len(out.Messages) != 1 {
        t.Fatalf("expected 1 message, got %d", len(out.Messages))
    }
}

DynamoDB example

func TestDynamoDBPutGet(t *testing.T) {
    ctx := context.Background()

    container, err := floci.NewFlociContainer().Start(ctx)
    if err != nil {
        t.Fatal(err)
    }
    defer container.Stop(ctx)

    cfg, _ := config.LoadDefaultConfig(ctx,
        config.WithRegion(container.GetRegion()),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            container.GetAccessKey(), container.GetSecretKey(), "",
        )),
        config.WithBaseEndpoint(container.GetEndpoint()),
    )

    client := dynamodb.NewFromConfig(cfg)

    _, err = client.CreateTable(ctx, &dynamodb.CreateTableInput{
        TableName:   aws.String("Orders"),
        BillingMode: types.BillingModePayPerRequest,
        AttributeDefinitions: []types.AttributeDefinition{
            {AttributeName: aws.String("id"), AttributeType: types.ScalarAttributeTypeS},
        },
        KeySchema: []types.KeySchemaElement{
            {AttributeName: aws.String("id"), KeyType: types.KeyTypeHash},
        },
    })
    if err != nil {
        t.Fatal(err)
    }

    _, err = client.PutItem(ctx, &dynamodb.PutItemInput{
        TableName: aws.String("Orders"),
        Item: map[string]types.AttributeValue{
            "id":     &types.AttributeValueMemberS{Value: "order-1"},
            "status": &types.AttributeValueMemberS{Value: "placed"},
        },
    })
    if err != nil {
        t.Fatal(err)
    }

    result, err := client.GetItem(ctx, &dynamodb.GetItemInput{
        TableName: aws.String("Orders"),
        Key: map[string]types.AttributeValue{
            "id": &types.AttributeValueMemberS{Value: "order-1"},
        },
    })
    if err != nil {
        t.Fatal(err)
    }

    status := result.Item["status"].(*types.AttributeValueMemberS).Value
    if status != "placed" {
        t.Errorf("expected status 'placed', got '%s'", status)
    }
}

Reusing the container across tests

Start the container once with TestMain and share it across all tests in the package:

var sharedContainer *floci.StartedFlociContainer

func TestMain(m *testing.M) {
    ctx := context.Background()

    var err error
    sharedContainer, err = floci.NewFlociContainer().Start(ctx)
    if err != nil {
        log.Fatalf("failed to start floci: %v", err)
    }

    code := m.Run()

    sharedContainer.Stop(ctx)
    os.Exit(code)
}

func awsCfg(ctx context.Context) aws.Config {
    cfg, _ := config.LoadDefaultConfig(ctx,
        config.WithRegion(sharedContainer.GetRegion()),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            sharedContainer.GetAccessKey(), sharedContainer.GetSecretKey(), "",
        )),
        config.WithBaseEndpoint(sharedContainer.GetEndpoint()),
    )
    return cfg
}

Options

Options are chained on the builder before calling Start:

container, err := floci.NewFlociContainer().
    WithRegion("eu-west-1").
    WithAccountID("123456789012").
    WithImage("floci/floci:1.6.0").
    WithDedicatedNetwork().
    Start(ctx)
Method Default Description
WithImage(image string) floci/floci:latest Docker image to use
WithRegion(region string) us-east-1 AWS region set in Floci and returned by GetRegion()
WithAccountID(id string) 000000000000 Default AWS account ID used in ARNs
WithAvailabilityZone(az string) us-east-1a Availability zone reported by Floci
WithDedicatedNetwork() (none) Create a dedicated Docker network for container-backed services (ElastiCache, RDS, OpenSearch, MSK)

Service configuration

Each service exposes a typed config struct. Pass it with the matching With<Service>Config method:

container, err := floci.NewFlociContainer().
    WithS3Config(floci.S3Config{
        Enabled:                    true,
        DefaultPresignExpirySeconds: 7200,
    }).
    WithSqsConfig(floci.SqsConfig{
        Enabled:                  true,
        DefaultVisibilityTimeout: 60,
        MaxMessageSize:           131072,
    }).
    WithDynamoDbConfig(floci.DynamoDbConfig{
        Enabled: true,
    }).
    Start(ctx)

Available config methods: WithAcmConfig, WithApiGatewayConfig, WithAppConfigConfig, WithAthenaConfig, WithBackupConfig, WithBedrockRuntimeConfig, WithCloudFormationConfig, WithCloudWatchLogsConfig, WithCloudWatchMetricsConfig, WithCodeBuildConfig, WithCodeDeployConfig, WithCognitoConfig, WithDynamoDbConfig, WithEc2Config, WithEcrConfig, WithEcsConfig, WithEksConfig, WithElastiCacheConfig, WithElbConfig, WithEventBridgeConfig, WithFirehoseConfig, WithGlueConfig, WithIamConfig, WithKinesisConfig, WithKmsConfig, WithLambdaConfig, WithMskConfig, WithOpenSearchConfig, WithRdsConfig, WithRoute53Config, WithS3Config, WithSchedulerConfig, WithSecretsManagerConfig, WithSesConfig, WithSnsConfig, WithSqsConfig, WithSsmConfig, WithStepFunctionsConfig, WithStsConfig, WithTextractConfig, WithTransferConfig.

StartedFlociContainer API

Method Returns Description
GetEndpoint() string Full HTTP endpoint, e.g. http://localhost:32768
GetRegion() string AWS region configured at start
GetAccountID() string AWS account ID configured at start
GetAccessKey() string Always "test"
GetSecretKey() string Always "test"
GetAvailabilityZone() string Availability zone configured at start
GetDedicatedNetworkName() string Docker network name, or empty string if not using a dedicated network
GetMappedPort(ctx, port int) (int, error) Host port mapped to the given container port
Stop(ctx) error Terminate the container and clean up

Source

github.com/floci-io/testcontainers-floci-go