Sobes.tech
Middle+

Vertel over contract testing van een microservice.

sobes.tech AI

Antwoord van AI

Contract testing richt zich op het controleren van de interactie tussen microservices op API-niveau (contract). Het doel is om te verzekeren dat de consument (service die API gebruikt) en de leverancier (service die API aanbiedt) zich aan een bepaald "contract" (verzoek/antwoord schema, datavormaat, verwachtingen) houden.

Belangrijke principes:

  • Consument-gedreven: Tests zijn gebaseerd op de verwachtingen van de consument. De consument bepaalt welke gegevens en formaten hij nodig heeft.
  • Tweerichtings testing: Controleert zowel of de consument (of hij met het huidige contract kan werken) als de leverancier (of zijn implementatie aan de verwachtingen voldoet).
  • Snelheid: Tests worden snel uitgevoerd, vaak binnen CI/CD pipelines, omdat ze niet alle afhankelijke services hoeven te implementeren.

Het proces omvat meestal:

  1. De consument maakt tests die zijn verwachtingen van de API beschrijven. Deze tests genereren een "contract".
  2. Het contract wordt gepubliceerd (bijvoorbeeld in een gecentraliseerde repository).
  3. De leverancier gebruikt het gepubliceerde contract om zijn API-implementatie te verifiëren. Hij voert tests uit (meestal aan de leverancierzijde) die controleren of zijn API aan het contract voldoet.
  4. Bij afwijkingen tussen de verwachtingen van de consument en de implementatie van de leverancier, faalt de test en wordt de contractbreuk aangegeven.

Tools:

  • Pact (populairst)
  • Spring Cloud Contract
  • Swagger/OpenAPI met validatietools

Voordelen:

  • Vroege detectie van integratiefouten: Problemen worden al vóór deployment ontdekt.
  • Vermindering van integratietests: Vermindert afhankelijkheid van complexe en langzame end-to-end tests.
  • Onafhankelijke testing: Maakt het mogelijk om services onafhankelijk van elkaar te testen.
  • Duidelijke definitie van verwachtingen: Definieert expliciet het contract tussen services.

Nadelen:

  • Vult niet volledig de integratietests (controleert alleen interactie op API-niveau).
  • Vereist discipline in versiebeheer van contracten.
  • Kan moeilijker zijn voor services met zeer complexe contracten.

Voorbeeld met Pact:

  1. De consument (bijvoorbeeld, Order Service) gebruikt de Pact-bibliotheek om consumer-side tests te maken.
    // Voorbeeld van een consumer-side test met Pact JVM
    @ExtendWith(PactConsumerTestExt.class)
    @PactTestFor(providerName = "ProductService", port = "8080")
    public class ProductServiceContractTest {
    
        @Pact(consumer = "OrderService")
        public RequestResponsePact createPact(PactDslWithRequest r) {
            return r.given("a product with id 1 exists")
                    .uponReceiving("a request for product by id")
                    .path("/products/1")
                    .method("GET")
                    .willRespondWith()
                    .status(200)
                    .headers(Map.of("Content-Type", "application/json"))
                    .body(new PactDslJsonBody()
                            .stringValue("id", "1")
                            .stringValue("name", "Laptop")
                            .numberType("price", 1200.00))
                    .toPact();
        }
    
        @Test
        void testGetProductById(MockServer mockServer) throws IOException {
            // API-aanroep logica
            HttpResponse response = Request.Get(mockServer.getUrl() + "/products/1").execute().returnResponse();
            assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
        }
    }
    
  2. Bij het uitvoeren van consumer-side tests wordt een contractbestand (.json) gegenereerd.
  3. Dit contractbestand wordt gepubliceerd naar Pact Broker of een andere gecentraliseerde repository.
  4. De leverancier (Product Service) gebruikt de Pact Provider Verifier bibliotheek om zijn API-implementatie te verifiëren op basis van het contract dat uit de Pact Broker is gehaald.
    // Voorbeeld van een leverancierstest met Pact Provider Verifier
    @Provider("ProductService")
    @PactFolder("relative/path/to/pacts") // of gebruik Pact Broker
    public class ProductServiceVerificationTest {
    
        @TestTemplate
        @ExtendWith(PactVerificationInvocationContextProvider.class)
        public void verifyPact(PactVerificationContext context) {
            context.verifyInteraction();
        }
    
        @State("a product with id 1 exists")
        public void productWithId1Exists() {
            // Logica voor het voorbereiden van de data-omgeving voor de test
        }
    
        // Voeg de URL van de service toe waar de test wordt uitgevoerd
        @BeforeEach
        void beforeEach(PactVerificationContext context) {
            context.setTarget(new HttpTestTarget("localhost", 8080));
        }
    }
    
  5. De verifier roept de echte API van de leverancier aan volgens de specificatie van het contract en vergelijkt het ontvangen antwoord met wat in het contract is verwacht.