Expertise hub / Techniek
Techniek Microservices 6 min lezen

Minder E2E. Meer vertrouwen. Snellere releases.

Elke service apart testen, zonder gedeelde testomgeving en zonder orkestratieprobleem. Dat is wat contract testing oplevert in een microservices architectuur, en het werkt anders dan de meeste teams denken.

Bij veel teams draait de integratietest nog altijd op hetzelfde recept: de hele stack opstarten, hopen dat alles samenwerkt, en drie uur wachten op een falende pipeline. Contract testing doorbreekt dat.

Met Pact test je niet of alles samen draait, maar of elke service zich houdt aan wat de andere ervan verwacht. In seconden, niet in minuten.

Hoe consumer-driven contracts werken

Het principe draait om drie stappen.

1. De consumer schrijft een contract

Service A, de consumer, legt vast welke request hij stuurt en welke response hij verwacht.

2. Het contract gaat naar de Pact Broker

Dat contract wordt gepubliceerd naar een centrale opslagplaats, de Pact Broker.

3. De provider draait de contracten als test

Service B, de provider, voert de contracten uit als test, zonder dat service A daarvoor actief hoeft te zijn.

Het resultaat: elke service wordt onafhankelijk getest, zonder gedeelde testomgeving en zonder orkestratieprobleem.

Een concreet voorbeeld met Pact-JS

Stel: de checkout-service (consumer) roept de user-service (provider) op voor klantgegevens. Zo ziet dat contract er in de praktijk uit.

consumer-test.jscheckout-service
// legt vast welke request/response checkout-service verwacht
const { PactV3 } = require('@pact-foundation/pact');

const provider = new PactV3({ consumer: 'checkout-service', provider: 'user-service' });

provider
  .given('gebruiker 123 bestaat')
  .uponReceiving('een aanvraag voor gebruiker 123')
  .withRequest({ method: 'GET', path: '/users/123' })
  .willRespondWith({
    status: 200,
    body: { id: 123, name: 'Jelle', email: 'jelle@qadvice.be' }
  });

it('haalt de gebruiker op', () =>
  provider.executeTest(async (mockserver) => {
    const res = await fetch(`${mockserver.url}/users/123`);
    expect(res.status).toBe(200);
  })
);

Deze test genereert automatisch het contract en publiceert het naar de Pact Broker:

pact-brokercheckout-service ↔ user-service
// ingekort contract, opgeslagen in de Pact Broker
{
  "consumer": { "name": "checkout-service" },
  "provider": { "name": "user-service" },
  "interactions": [{
    "description": "een aanvraag voor gebruiker 123",
    "request": { "method": "GET", "path": "/users/123" },
    "response": {
      "status": 200,
      "body": { "id": 123, "name": "Jelle", "email": "jelle@qadvice.be" }
    }
  }]
}

De provider verifieert dat contract zonder dat checkout-service ooit moet draaien:

provider-test.jsuser-service
// speelt alle gepubliceerde contracten af tegen de echte provider
const { Verifier } = require('@pact-foundation/pact');

new Verifier({
  provider: 'user-service',
  providerBaseUrl: 'http://localhost:3001',
  pactBrokerUrl: 'https://pact.qadvice.be',
  publishVerificationResult: true,
  providerVersion: '1.4.2'
}).verifyProvider();

Geeft user-service het veld email niet meer terug? Dan faalt de verificatie meteen, en weet je exact welke service en welk veld het probleem zijn, zonder dat checkout-service ooit moest opstarten.

Wat het oplevert in de pipeline

Seconden
geen volledige stack nodig
Precies
foutlokalisatie per service
Onafhankelijk
deploys per team

Faalt een contracttest, dan is meteen duidelijk welke service welk veld niet meer levert en wie dat moet fixen. De Pact Broker biedt bovendien een can-i-deploy check: is het veilig om deze versie te releasen, gegeven alle contracten die ermee samenhangen? Het resultaat is minder flaky tests, minder wantrouwen in de suite, en een snellere releasecyclus.

Wanneer je contract testing inzet

Contract testing is sterk voor REST API's, dus GET en POST interfaces tussen services, en werkt ook voor async messaging zoals Kafka, RabbitMQ of SNS/SQS. Voor complexe, stateful multi-step interacties is het minder geschikt. En voor kritieke gebruikersflows blijft end-to-end testen nodig, contract testing vervangt dat niet.

Vuistregel: contract testing voor interfaces, end-to-end voor gebruikersscenario's.

Drie valkuilen om te vermijden

Verouderde contracten

Contracten die niet mee-evolueren met de code geven een vals gevoel van veiligheid. Een contract dat niemand nog bijwerkt, test niets zinvols meer.

Contracten te strak definiëren

Over-specificeren maakt de provider onnodig fragiel. Test enkel wat de consumer echt gebruikt, niet elk veld dat toevallig in de response zit.

Alle end-to-end tests weggooien

Contract testing valideert interfaces, geen business logica. Hou een slanke end-to-end suite voor de paden die er echt toe doen.

Contract testing werkt het best als aanvulling op een doordachte teststrategie, niet als vervanging ervan. Minder end-to-end, meer vertrouwen, snellere releases: dat is wat het oplevert wanneer je het goed inzet.

Carrousel: contract testing met Pact
// Ook als carrousel

Dit artikel verscheen eerder als carrousel op LinkedIn.

Bekijk op LinkedIn
// Verder praten

Herkenbaar?

We nemen je testaanpak door en zeggen eerlijk waar de winst zit. En waar niet.

Plan een gesprek
// Lees ook