Testing in EDA: come verificare ciò che non vedi 🧪
In un sistema sincrono, se il test passa hai una certa fiducia. In un sistema event-driven, il test può passare e il sistema comunque fallire in produzione — perché hai testato il publisher senza verificare che il consumer sappia interpretare quello che riceve. Questa guida affronta esattamente questo problema.
La piramide di testing adattata all’EDA 🔺
La piramide classica (unit → integration → e2e) si adatta così:
flowchart TD
subgraph Alto
E2E[E2E — flussi di business completi]
end
subgraph Medio
INT[Integration test con broker reale]
CC[Consumer contract test]
end
subgraph Base
UNIT[Unit test — logica handler]
SCHEMA[Schema validation test]
end
Il livello più critico nei sistemi EDA è quello medio: i contract test e i test di integrazione con broker reale rilevano il 90% dei bug di interfaccia tra servizi — quelli che le unit test non possono intercettare perché testano un solo lato.
Unit test: isola l’handler 🔬
Il consumer è prima di tutto una funzione (evento) → side effect. È testabile in isolamento, senza broker.
|
|
Da coprire con unit test:
- logica di business del consumer;
- mapping evento → modello interno;
- casi di errore (campo mancante, valore invalido, tipo inatteso);
- idempotenza: processare lo stesso evento due volte deve produrre lo stesso risultato.
Schema validation test 📋
Prima che un evento arrivi in produzione, verifica che rispetti lo schema dichiarato — e che le versioni siano compatibili tra loro.
|
|
Integra la schema validation nel CI: un breaking change deve fare fallire la pipeline, non arrivare in staging.
Consumer contract testing con Pact 🤝
Il consumer contract testing verifica che producer e consumer concordino sul contratto dell’evento. Il test viene scritto dal consumer (che dichiara cosa si aspetta) e verificato dal producer (che deve soddisfare le aspettative dichiarate).
flowchart LR
C["Consumer<br/>(scrive il test)"] -->|genera| PACT["Pact file<br/>(contratto JSON)"]
PACT --> PB[(Pact Broker)]
P[Producer] -->|verifica| PB
P -->|pubblica risultato| PB
PB -->|can-i-deploy?| C
PB -->|can-i-deploy?| P
Flusso pratico:
- Il team consumer scrive un test che dichiara: “mi aspetto un evento
OrdineCreatocon questi campi e questi tipi”. - Pact genera un pact file (JSON) che descrive il contratto.
- Il pact file viene pubblicato su un Pact Broker.
- Nel CI del producer, si esegue la verifica: il producer deve produrre un messaggio che soddisfa tutti i pact registrati.
- Prima del deploy, si esegue
can-i-deploy?per bloccare il rilascio se esistono consumer rotti.
Cosa non è Pact: non testa il comportamento del broker e non è un test e2e. Verifica solo il contratto tra producer e consumer — che è esattamente quello che di solito manca.
Strumenti: Pact (Go, Java, JS, Python, .NET), PactFlow per il broker hosted con UI.
Integration test con broker reale 🧰
Per testare il flusso completo (producer → broker → consumer) serve un broker reale. Testcontainers permette di avviare Kafka, RabbitMQ, ecc. come container Docker durante i test.
|
|
Strumenti:
- Testcontainers per Go, Java, Python, Node.js.
- LocalStack per AWS SQS, SNS, Kinesis.
- Embedded Kafka (Spring) per test più leggeri in ambienti Spring/Java.
Test di idempotenza ↩️
Un consumer idempotente deve produrre lo stesso risultato se riceve lo stesso evento più volte. Questo va testato esplicitamente — non basta assumerlo.
|
|
Test di error handling: retry e DLQ 🧯
Verifica che il comportamento su errore sia quello atteso:
- un evento con payload invalido finisce in DLQ dopo N tentativi;
- un errore transitorio provoca retry con backoff e poi successo;
- il retry non rompe l’idempotenza (processo riuscito al secondo tentativo = stesso stato di un successo al primo).
|
|
Test di ordering (quando è un requisito esplicito) 🧵
Se l’ordering per chiave è un requisito del sistema, testalo con scenari multi-messaggio. Pubblica N eventi per la stessa chiave in ordine, verifica che il consumer li riceva nello stesso ordine.
Questo test è spesso mancante e si scopre il problema solo in produzione, durante un reprocessing o un failover.
Checklist testing EDA ✅
- Unit test per ogni consumer handler (logica, mapping, casi di errore, idempotenza).
- Schema validation test in CI (verifica compatibilità tra versioni di schema).
- Consumer contract test (Pact o equivalente) per i contratti tra team.
- Integration test con broker reale (Testcontainers / LocalStack) almeno per i flussi critici.
- Test espliciti di idempotenza su duplicati.
- Test di error handling: errori transitori (retry + backoff), errori permanenti (DLQ).
- Test di ordering per i flussi che lo richiedono.
- Ambiente di staging con replay controllato per validare nuove proiezioni o nuovi consumer.
Prossimi passi 🚀
- Per idempotenza e DLQ in produzione: vedi la guida su operatività.
- Per schema evolution e compatibilità: vedi la guida su Schema Registry.
- Per governance e ownership dei contratti: vedi la guida di governance.