Een recente deployment zorgde ervoor dat drie microservices defect raakten, ook al slaagden alle unit tests, integratietests en mock-server controles. Het team verving hun API-mocks door contract testing. In zes maanden tijd steeg het aantal contracten van drie naar 47 en daalde het maandelijkse percentage integratiefouten van twee incidenten naar nul.
Waarom mocks je niet kunnen beschermen
Een mock-server bootst alleen de vorm na die een consument verwacht; het controleert nooit of de provider daadwerkelijk die vorm levert. Als een provider een veld hernoemt – bijvoorbeeld van name naar display_name – geeft de mock nog steeds de oude payload terug, blijven de tests van de consument groen en crasht het live systeem. De productiefout op een dinsdag om 14:00 uur was precies dat: de mock "loog" over het echte contract.
Contract testing vult het gat
Contract testing dwingt de twee kanten van een API om het eens te worden over een gedeelde definitie voordat er code in productie gaat. Er bestaan twee veelvoorkomende benaderingen:
- Consumer-driven contracts – de consumerende service schrijft verwachtingen; de providerende service valideert deze. Dit werkt goed voor interne microservices die samen evolueren.
- Provider-driven contracts – de provider publiceert een specificatie; consumenten controleren hun code daaraan. Dit is het gebruikelijke patroon voor publieke API's.
De eerste benadering voorkomt over het algemeen defecte integraties binnen een microservice-architectuur.
Hoe een consumer-driven contract werkt
- De consument schrijft een test die precies beschrijft wat hij van de provider nodig heeft.
- Het uitvoeren van de test genereert een pact file – een JSON-document dat deze verwachtingen vastlegt.
- De provider draait zijn echte service tegen het pact file in zijn CI-pipeline.
- Als de provider een veld wijzigt, mislukt de verificatie en wordt de build geblokkeerd.
Omdat de verificatie op de daadwerkelijke provider-code wordt uitgevoerd, wordt elke breaking change vroegtijdig opgemerkt, en niet pas na de deployment.
Waar contract tests horen in je testpiramide
- Unit tests – snel, testen geïsoleerde logica.
- Contract tests – gemiddelde snelheid, bevestigen dat API-overeenkomsten standhouden.
- End-to-end tests – traag, testen volledige business flows.
Beschouw contract tests als een brug tussen de snelle feedback van unit tests en de brede dekking van end-to-end suites. Richt je op de integratiepunten die het vaakst kapot gaan en begin met twee of drie kritieke endpoints.
Een praktijkvoorbeeld
Het team dat aan de basis staat van dit artikel, begon met drie contracten die hun meest kwetsbare aanroepen dekten. Zes maanden later hadden ze 47 contracten die het merendeel van het verkeer tussen services dekten. In die periode daalde het aantal incidenten met API-breuken van twee per maand naar nul.
Wanneer contract testing het misschien niet waard is
- Je bent een solo-ontwikkelaar die alle services in één enkele repository beheert.
- De API is uitzonderlijk stabiel en is al jaren niet veranderd.
- Je bouwt een prototype dat bedoeld is om snel weer weg te gooien.
In die scenario's kan de overhead van het onderhouden van contracten zwaarder wegen dan het voordeel.
Mogelijke nadelen en hoe je deze kunt beperken
- Houd contracten versied naast de code die ze beschrijven.
- Automatiseer verificatie bij elke CI-run om verouderde contracten te voorkomen.
- Beoordeel wijzigingen in contracten in pull requests om onbedoelde breuken op te merken.
Conclusie
Als je nog steeds vertrouwt op handgemaakte mocks om jezelf ervan te overtuigen dat je services met elkaar kunnen communiceren, gok je op een valse belofte. Contract testing verandert die gok in een verifieerbare overeenkomst, waardoor breaking changes worden opgevangen voordat ze de productie bereiken en, zoals de cijfers van het team laten zien, integratiefouten volledig kan elimineren.
