jguillaumesio
devopsaws

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.

Pipeline de test local Lambda + EventBridge

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 LocalStackPiège
Invocation Lambda✅ BonDémarrages à froid non simulés
Règles EventBridge✅ BonCertains cas limites de correspondance diffèrent
Permissions IAM⚠️ PartielN’applique pas toutes les politiques
Concurrence Lambda❌ NonPas de simulation de throttling
Réseau VPC⚠️ PartielLa création d’ENI diffère
Métriques CloudWatch⚠️ BasiqueCertaines 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.