O Que São Testcontainers?
Testcontainers é uma biblioteca que facilita o uso de containers Docker para testes automatizados. Cada teste consegue seu próprio container isolado.
Benefícios
- Isolamento – Cada teste tem seu ambiente
- Reprodutibilidade – Mesmo ambiente sempre
- Realismo – Testa contra serviços reais
- Cleanup automático – Containers são removidos
Setup
org.testcontainers
testcontainers
1.19.3
pip install testcontainers
go get github.com/testcontainers/testcontainers-go
npm install @testcontainers
Exemplos
PostgreSQL (Python)
# test_database.py
import pytest
from testcontainers.postgres import PostgresContainer
import psycopg2
@pytest.fixture(scope="module")
def postgres():
with PostgresContainer("postgres:15-alpine") as pg:
yield pg
def test_database_connection(postgres):
# Get connection string
connection_url = postgres.get_connection_url()
# Connect
conn = psycopg2.connect(connection_url)
cursor = conn.cursor()
# Test
cursor.execute("SELECT version();")
version = cursor.fetchone()
assert "PostgreSQL" in version[0]
cursor.close()
conn.close()
def test_create_table(postgres):
conn = psycopg2.connect(postgres.get_connection_url())
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL
)
""")
conn.commit()
# Insert test data
cursor.execute("""
INSERT INTO users (name, email)
VALUES ('João', '[email protected]')
RETURNING id
""")
user_id = cursor.fetchone()[0]
# Verify
cursor.execute("SELECT * FROM users WHERE id = {6727d158e4474b0847515bd23a8ee4bebd5f1aac7a18bc968e51d8985dccb442}s", (user_id,))
user = cursor.fetchone()
assert user[1] == "João"
cursor.close()
conn.close()
Redis (Python)
# test_redis.py
import pytest
from testcontainers.redis import RedisContainer
import redis
@pytest.fixture
def redis_container():
with RedisContainer("redis:7-alpine") as redis:
yield redis
def test_redis_basic_operations(redis_container):
client = redis.Redis.from_url(redis_container.get_connection_url())
# Set and get
client.set("key", "value")
assert client.get("key") == b"value"
# Expire
client.setex("temp", 60, "temp_value")
assert client.get("temp") == b"temp_value"
# List
client.lpush("mylist", "a", "b", "c")
assert client.lrange("mylist", 0, -1) == [b'c', b'b', b'a']
client.flushall()
def test_redis_pubsub(redis_container):
client = redis.Redis.from_url(redis_container.get_connection_url())
# Pub/Sub pattern
pubsub = client.pubsub()
pubsub.subscribe("test-channel")
# Give time for subscription
import time
time.sleep(0.5)
# Publish message
client.publish("test-channel", "Hello, World!")
# Receive message
message = pubsub.get_message()
assert message["type"] == "message"
assert message["data"] == b"Hello, World!"
pubsub.close()
MongoDB (Java)
// MongoDBTest.java
import org.testcontainers.containers.MongoDBContainer;
import org.junit.jupiter.api.*;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import static org.junit.jupiter.api.Assertions.*;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MongoDBTest {
MongoDBContainer mongo = new MongoDBContainer("mongo:7");
@BeforeAll
void startContainer() {
mongo.start();
}
@AfterAll
void stopContainer() {
mongo.stop();
}
@Test
void testMongoConnection() {
String connectionString = mongo.getReplicaSetUrl();
try (var client = MongoClients.create(connectionString)) {
MongoDatabase database = client.getDatabase("testdb");
MongoCollection collection = database.getCollection("users");
// Insert document
org.bson.Document user = new org.bson.Document()
.append("name", "João Silva")
.append("email", "[email protected]");
collection.insertOne(user);
// Find document
org.bson.Document found = collection.find().first();
assertNotNull(found);
assertEquals("João Silva", found.getString("name"));
}
}
}
Docker Compose (Python)
# test_integration.py
import pytest
from testcontainers.compose import DockerCompose
@pytest.fixture(scope="module")
def compose():
with DockerCompose("/path/to/project") as compose:
compose.start()
yield compose
compose.stop()
def test_all_services_running(compose):
# Check if services are healthy
exit_code, output = compose.exec("api", "curl -f http://localhost:8080/health")
assert exit_code == 0
exit_code, output = compose.exec("db", "pg_isready")
assert exit_code == 0
def test_api_with_database(compose):
# API should be able to connect to database
exit_code, output = compose.exec(
"api",
"python -c 'import psycopg2; print("OK")'"
)
assert exit_code == 0
assert "OK" in output.decode()
Multiple Containers
# test_full_stack.py
import pytest
from testcontainers.postgres import PostgresContainer
from testcontainers.redis import RedisContainer
from testcontainers.kafka import KafkaContainer
@pytest.fixture(scope="module")
def test_environment():
# Start all containers
postgres = PostgresContainer("postgres:15")
redis = RedisContainer("redis:7")
kafka = KafkaContainer()
# Start them
pg_url = postgres.start()
redis_url = redis.start()
kafka_bootstrap = kafka.start()
yield {
'postgres': pg_url,
'redis': redis_url,
'kafka': kafka_bootstrap
}
# Cleanup
kafka.stop()
redis.stop()
postgres.stop()
CI Integration
# GitHub Actions
name: Tests with Testcontainers
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
docker:
image: docker:24.0.5-cli
options: --privileged
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Run tests
run: |
docker network create testnet || true
docker compose -f docker-compose.test.yml up -d
docker compose -f docker-compose.test.yml run test
Conclusão
Testcontainers facilita testes com dependências reais. As chaves são:
- Isolamento – Cada teste com seu ambiente
- Realismo – Testa contra serviços reais
- Cleanup – Containers são removidos após testes
- CI – Funciona em pipelines
- Performance – Mais lento que mocks, mais confiável
