Integration testing modern dengan Floci: FLociContainer bawaan Testcontainers untuk Java, pola setara di Node/Python/Go dengan generic container + wait strategy — spin-up per test suite, endpoint dinamis, dan cleanup otomatis

Sejauh ini kita menjalankan floci secara manual. Untuk integration testing, pola yang lebih baik adalah: test suite sendiri yang menyalakan dan mematikan emulator — endpoint dinamis, state bersih per run, nol konfigurasi manual. Itulah domain Testcontainers.
Kabar baiknya: Testcontainers punya modul FLociContainer resmi untuk Java, dan pola identik bisa dicapai di Node/Python/Go lewat generic container. Episode ini membedah keduanya.
Untuk ekosistem JVM (JUnit 5):
import org.floci.testcontainers.FLociContainer;
import org.junit.jupiter.api.Test;
import software.amazon.awssdk.services.s3.S3Client;
class OrderRepositoryIT {
static FLociContainer floci = new FLociContainer(DockerImageName.parse("floci/floci:1.5.15"));
@Test
void savesOrder() {
S3Client s3 = S3Client.builder()
.endpointOverride(floci.getEndpoint())
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("test", "test")))
.region(Region.US_EAST_1).build();
s3.createBucket(b -> b.bucket("orders-test"));
// ... repository.save(order) -> assert di s3.getObject(...)
}
}Yang diberikan container resmi secara gratis:
floci/floci:1.5.15).getEndpoint() mengembalikan host:port acak dari Docker.Ekosistem tanpa modul khusus tetap bisa memakai generic container + wait strategy:
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_container_is_ready
import boto3
def floci():
return (DockerContainer("floci/floci:1.5.15")
.with_exposed_ports(4566)
.with_bind_ports(4566, 0)) # port host dinamis
with floci() as fc:
port = fc.get_exposed_port(4566)
s3 = boto3.client("s3", endpoint_url=f"http://localhost:{port}")
s3.create_bucket(Bucket="py-it")
assert [b["Name"] for b in s3.list_buckets()["Buckets"]] == ["py-it"]
print("python IT OK")Tiga disiplin yang membuat integration test stabil:
| Pola | Implementasi | Manfaat |
|---|---|---|
| Spin-up per suite | Container dibuat di fixture beforeAll/module scope | Satu startup untuk banyak test |
| Endpoint dinamis | Jangan hardcode 4566 — selalu ambil mapped port | Parallel CI jobs tanpa bentrok |
| Cleanup otomatis | Context manager / try-finally stop container | Tidak ada container zombie di runner |
Tip
Gunakan image tag pinned (mis. 1.5.15), bukan latest — test harus reproducible; upgrade emulator adalah keputusan eksplisit dengan re-run suite sebagai gerbang.
Target outline: integration test repository layer app melawan DynamoDB/SQS floci.
import pytest
import boto3
from testcontainers.core.container import DockerContainer
@pytest.fixture(scope="session")
def aws():
with DockerContainer("floci/floci:1.5.15").with_exposed_ports(4566) as fc:
ep = f"http://localhost:{fc.get_exposed_port(4566)}"
yield {
"ddb": boto3.resource("dynamodb", endpoint_url=ep),
"sqs": boto3.client("sqs", endpoint_url=ep),
}
class OrderRepo:
def __init__(self, ddb, table): self.tbl = ddb.Table(table)
def save(self, order): self.tbl.put_item(Item=order)
def get(self, oid):
return self.tbl.get_item(Key={"pk": oid}).get("Item")
def test_save_and_get_roundtrip(aws):
# arrange: tabel dibuat oleh SUITE (state bersih terjamin)
table = aws["ddb"].create_table(
TableName="it-orders", BillingMode="PAY_PER_REQUEST",
AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"}])
table.wait_until_exists()
repo = OrderRepo(aws["ddb"], "it-orders")
repo.save({"pk": "ORD-1", "status": "new"})
assert repo.get("ORD-1")["status"] == "new"
def test_isolation_between_tests(aws):
# bukti state tidak bocor antar test dalam satu suite
resp = aws["sqs"].list_queues()
assert "QueueUrls" not in resp or not any("it-q" in u for u in resp["QueueUrls"])Perhatikan test kedua: ia membuktikan properti paling berharga emulator-per-suite — tidak ada state bocor antar test, sesuatu yang sulit dijamin dengan environment AWS bersama.
Rangkuman episode ini:
FLociContainer resmi; Node/Python/Go memakai generic container + wait strategy (health endpoint).Episode 21 membawa semua ini ke pipeline: integrasi CI/CD — service container GitHub Actions/GitLab, disiplin pin versi, cache layer, dan parallel job isolation. Sampai jumpa!