Testy kontraktowe: Pact bez Pact Brokera

pact-jvm to jeden z dwóch wiodących frameworków do pisania testów kontraktowych dla Javy i JVM. Problemem na wejściu może być znalezienie sposobu na dzielenie się kontraktami: oficjalnie polecanym rozwiązaniem jest serwer Pact Broker, napisany w Rubym. Postawienie serwera trochę ułatwia dostępność obrazu dockerowego, ale wciąż całość wymaga nieco zabawy. Jeśli chcemy na szybko sprawdzić, czy Pact nam się podoba, wolelibyśmy opcję wymagającą minimalnego nakładu pracy.

Tu pojawia się możliwość rzadko wspominana w tutorialach Pacta: wymiana kontraktów przez repozytorium Mavena, nawet takie lokalnie na dysku. Nie potrzebujemy niczego poza narzędziami, które i tak mamy programując w Javie.

Załóżmy, że mamy następujący kontrakt po stronie konsumenta:

@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "MyProvider")
public class ContractTest {

    @Pact(consumer = "MyConsumer")
    public RequestResponsePact wrongBooking(PactDslWithProvider builder) {

Wtedy po pomyślnym wykonaniu testów wygenerowany kontrakt w formie JSON-a ląduje na dysku w katalogu naszego projektu, a dokładniej w build/pacts/MyConsumer-MyProvider.json. Musimy tylko skonfigurować Gradle’a po stronie konsumenta, żeby kontrakty zostały opakowane w mavenowy artefakt, który będziemy tak jak każdy inny wersjonować i publikować:

plugins {
    id 'java-library'
    id 'maven-publish'
}

group = 'com.example'
version = '1.0.1-SNAPSHOT'

task pactsJar(type: Jar, dependsOn: test) {
    from(buildDir)
    include("pacts/**")
}

publishing {
    publications {
        pact(MavenPublication) {
            artifactId = "pacts"
            artifact(pactsJar)
        }
    }
}

Po odpaleniu gradle publish kontrakty trafiają do ~/.m2/repository/com/example/pacts. Dla prostoty używamy tu lokalnego repozytorium, ale publikacja zadziała z dowolnym repozytorium Mavena na serwerze.

Po stronie producenta konfigurujemy Gradle’a, by JAR z kontraktami został pobrany i był wczytany na czas uruchamiania testów:

repositories {
    jcenter()
    mavenLocal()
}

dependencies {
    testRuntimeOnly 'com.example:pacts:1.0.1-SNAPSHOT'
}

Zostaje tylko wskazanie w teście, by kontrakt był czytany z classpath:

@Provider("MyProvider")
@PactUrl(urls = {"classpath:pacts/MyConsumer-MyProvider.json"})
public class ContractVerificationTest {

Pełny działający projekt można zobaczyć w moim repozytorium z przykładami testów kontraktowych: https://github.com/pkubowicz/contract-testing-samples. Na YouTube jest dostępne nagranie prezentacji „Flying start into contract testing” z konferencji Heisenbug, gdzie pokazuję na żywo, jak w tych przykładowych projektach działa Pact.