Tester Lambda + EventBridge en local
Comment tester des fonctions AWS Lambda et des règles EventBridge en local avec SAM, LocalStack et docker-compose. Aucun déploiement cloud nécessaire.
Vous changez une ligne dans une Lambda. Vous déployez sur AWS. Vous attendez 30 secondes. Vous testez. Ça échoue. Vous changez une ligne. Vous déployez. Vous attendez. Ce cycle est lent, coûteux et inutile.
Voici comment tester Lambda + EventBridge entièrement sur votre portable.
Le setup : docker-compose + LocalStack
L’approche la plus simple utilise LocalStack, une stack cloud AWS complète qui tourne dans Docker.
# docker-compose.yml
version: "3.8"
services:
localstack:
image: localstack/localstack:3.4
ports:
- "4566:4566" # Passerelle LocalStack
- "4510-4559:4510-4559" # plage de ports des services externes
environment:
- SERVICES=lambda,events,logs,iam,sts
- DEFAULT_REGION=eu-west-3
- LAMBDA_EXECUTOR=docker-reuse
- LAMBDA_REMOTE_DOCKER=false
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "./localstack-init:/etc/localstack/init/ready.d"
Démarrez-le :
docker compose up -d
Créer une Lambda en local
# Empaqueter votre fonction
cd src/handler && zip -r ../../function.zip . && cd ..
# Créer la Lambda
aws --endpoint-url=http://localhost:4566 lambda create-function \
--function-name process-order \
--runtime python3.12 \
--handler handler.lambda_handler \
--zip-file fileb://function.zip \
--role arn:aws:iam::000000000000:role/lambda-role \
--timeout 30 \
--memory-size 256
# L'invoquer
aws --endpoint-url=http://localhost:4566 lambda invoke \
--function-name process-order \
--payload '{"orderId": "12345", "action": "process"}' \
--cli-binary-format raw-in-base64-out \
output.json
cat output.json
Mettre en place des règles EventBridge
# Créer un bus d'événements
aws --endpoint-url=http://localhost:4566 events create-event-bus \
--name order-events
# Créer une règle
aws --endpoint-url=http://localhost:4566 events put-rule \
--name order-created-rule \
--event-bus-name order-events \
--event-pattern '{"source": ["order.service"], "detail-type": ["Order Created"]}'
# Ajouter la Lambda comme cible
aws --endpoint-url=http://localhost:4566 events put-targets \
--rule order-created-rule \
--event-bus-name order-events \
--targets "Id"="1","Arn"="arn:aws:lambda:eu-west-3:000000000000:function:process-order"
Tester le flux complet
# Envoyer un événement de test
aws --endpoint-url=http://localhost:4566 events put-events \
--entries '[
{
"Source": "order.service",
"DetailType": "Order Created",
"Detail": "{\"orderId\": \"ORD-001\", \"amount\": 99.99, \"customer\": \"alice@example.com\"}",
"EventBusName": "order-events"
}
]'
# Vérifier les logs de la Lambda
aws --endpoint-url=http://localhost:4566 logs filter-log-events \
--log-group-name /aws/lambda/process-order \
--limit 10
Tests automatisés avec pytest
# tests/test_process_order.py
import json
import boto3
import pytest
@pytest.fixture
def lambda_client():
return boto3.client(
"lambda",
endpoint_url="http://localhost:4566",
region_name="eu-west-3",
aws_access_key_id="test",
aws_secret_access_key="test",
)
@pytest.fixture
def events_client():
return boto3.client(
"events",
endpoint_url="http://localhost:4566",
region_name="eu-west-3",
aws_access_key_id="test",
aws_secret_access_key="test",
)
def test_process_order_success(lambda_client):
response = lambda_client.invoke(
FunctionName="process-order",
Payload=json.dumps({
"orderId": "ORD-001",
"action": "process"
}),
)
payload = json.loads(response["Payload"].read())
assert response["StatusCode"] == 200
assert payload["status"] == "processed"
def test_eventbridge_triggers_lambda(events_client, lambda_client):
# Envoyer l'événement
events_client.put_events(Entries=[{
"Source": "order.service",
"DetailType": "Order Created",
"Detail": json.dumps({"orderId": "ORD-002", "amount": 50.0}),
"EventBusName": "order-events",
}])
# Attendre le traitement asynchrone
import time; time.sleep(2)
# Vérifier via CloudWatch Logs
logs = boto3.client("logs", endpoint_url="http://localhost:4566",
region_name="eu-west-3",
aws_access_key_id="test",
aws_secret_access_key="test")
events = logs.filter_log_events(
logGroupName="/aws/lambda/process-order",
limit=5,
)
log_messages = [e["message"] for e in events["events"]]
assert any("ORD-002" in msg for msg in log_messages)
Lancez-le :
docker compose up -d
./scripts/setup-localstack.sh # créer fonctions, règles, cibles
pytest tests/ -v
Alternative : AWS SAM Local
Si vous utilisez déjà SAM :
# Démarrer l'API locale et le runtime Lambda
sam local start-lambda --docker-network host
# Invoquer
sam local invoke process-order -e events/order-created.json
# Tester la correspondance de motif EventBridge
sam local generate-event events put-events \
--source order.service \
--detail-type "Order Created" \
--detail '{"orderId": "12345"}'
SAM est plus léger que LocalStack mais ne gère bien que Lambda + API Gateway. Pour EventBridge, LocalStack est meilleur.
Ce que LocalStack ne simule pas bien
Soyez conscient des lacunes :
| Fonctionnalité | Support LocalStack | Piège |
|---|---|---|
| Invocation Lambda | ✅ Bon | Démarrages à froid non simulés |
| Règles EventBridge | ✅ Bon | Certains cas limites de correspondance diffèrent |
| Permissions IAM | ⚠️ Partiel | N’applique pas toutes les politiques |
| Concurrence Lambda | ❌ Non | Pas de simulation de throttling |
| Réseau VPC | ⚠️ Partiel | La création d’ENI diffère |
| Métriques CloudWatch | ⚠️ Basique | Certaines dimensions de métriques manquent |
Pour les tests d’intégration, LocalStack convient. Pour les tests de performance ou de concurrence, il vous faut le vrai cloud.
Le workflow
1. Écrire le code de la Lambda
2. docker compose up -d # démarrer LocalStack
3. ./scripts/setup.sh # déployer fonctions + règles en local
4. pytest tests/ -v # lancer les tests
5. docker compose down # nettoyer
Temps de cycle total : moins de 5 secondes par exécution de test. Aucun déploiement cloud. Aucune attente. Aucune facture AWS pour les tests.
Quand LocalStack ne suffit pas
Ce setup couvre la logique, le routage d’événements et les tests d’intégration, le gros de ce qui ralentit une boucle déploie-puis-teste. Il ne remplace pas un environnement de staging si votre Lambda dépend de :
- Cas limites de permissions IAM (LocalStack est permissif par défaut)
- Quotas de service réels ou comportement de throttling
- Routage d’événements cross-région ou cross-compte
Pour une petite équipe qui livre une poignée de Lambdas, la mise en place de 10 minutes se rentabilise dès le premier après-midi. Pour tout ce qui touche à la justesse des politiques IAM, budgétez un déploiement en staging avant de fusionner : aucun outil local ne remplace cette vérification.
Les tests locaux attrapent les défaillances faciles. Pour celles qui sont silencieuses et n’apparaissent qu’en production, voir Le débogage compte plus que les fonctionnalités pour la surveillance qui rattrape ce que les tests locaux manquent.