Contract testing met Pactflow (deel 2/3) : Consumer test

Zie ook:
Contract testing met Pactflow (deel 1/3) : Contract test process
Contract testing met Pactflow (deel 2/3) : Consumer test
Contract testing met Pactflow (deel 3/3) : Provider test

In deze blog laat ik een voorbeeld zien hoe je als consumer microservice een contract test kunt maken. Hierbij ga ik er vanuit dat je Jenkins gebruikt en een account hebt bij pactflow die je gebruikt als pact-broker. De code is in Kotlin.

Voeg deze dependency toe aan de pom.xml:

<dependency>
  <groupId>au.com.dius.pact.consumer</groupId>
  <artifactId>junit5</artifactId>
  <version>4.1.7</version>
  <scope>test</scope>
</dependency>

Voeg deze plugin to aan de pom.xml:

<plugin>
    <groupId>au.com.dius</groupId>
    <artifactId>pact-jvm-provider-maven</artifactId>
    <version>4.0.10</version>
    <configuration>
        <pactBrokerUrl>https://xxx.pactflow.io</pactBrokerUrl>
        <projectVersion>${pact.consumer.version}</projectVersion>
        <pactBrokerToken>${pact.token}</pactBrokerToken>
        <tags>
            <tag>${pact.tag}</tag>
        </tags>
    </configuration>
</plugin>

(verander xxx.pactflow.io naar je eigen referentie in pactflow)

Voeg onderstaande variabele toe in de environment stap in de Jenkinsfile:

PACTFLOW_TOKEN = credentials('jenkins-pactflowtoken')

(Hierbij is het nodig dat er een ‘jenkins-pactflowtoken’ secret in Jenkins is aangemaakt met daarin het token dat je van pactflow gekregen hebt)

Voeg onderstaande stap toe direct na de test stap in de Jenkinsfile:

stage('Upload contract') {
  steps {
   sh "./mvnw pact:publish -Dpact.consumer.version=${GIT_COMMIT} -Dpact.tag=${BRANCH_NAME} -Dpact.token=${PACTFLOW_TOKEN}"
  }
}

Voeg onderstaande stap toe vlak voor het deployen naar prd:

stage('Can I deploy to prd?') {
  steps {
   sh "./mvnw au.com.dius:pact-jvm-provider-maven:4.0.10:can-i-deploy -Dpacticipant=$APP_NAME -Dpactbroker.host=https://alliander.pactflow.io -DpacticipantVersion=${GIT_COMMIT} -Dpact.token=$PACTFLOW_TOKEN -DtoTag=master"
  }
}

(deze stap voeren we dus in alle branches uit, maar voor tst en acc is het geen voorwaarde dat de can_i_deploy goed gaat, dat is alleen voor productie een voorwaarde)

Voorbeeld unit test voor een succesvolle call:

import Customer
import CustomerClient
import au.com.dius.pact.consumer.MockServer
import au.com.dius.pact.consumer.dsl.PactDslJsonBody
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.core.model.RequestResponsePact
import au.com.dius.pact.core.model.annotations.Pact
import com.alliander.sample.app.contract.TestUtil
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.io.IOException

@ExtendWith(PactConsumerTestExt::class)
@PactTestFor(providerName = "sample-service-2")
class FoundTest {

    @Pact(provider = "sample-service-2", consumer = "sample-service-1")
    fun createPact(builder: PactDslWithProvider): RequestResponsePact {
        return builder
            .given("default", mapOf("customerExists" to true))
            .uponReceiving("a request an existing customer")
            .path("/1")
            .method("GET")
            .willRespondWith()
            .status(200)
            .body(
                PactDslJsonBody()
                    .stringType("firstName")
                    .stringType("surName")
                    .stringType("lastName")
            )
            .toPact()
    }

    @Test
    @Throws(IOException::class)
    fun testContract(mockServer: MockServer) {
        val customerClient = CustomerClient(mockServer.getUrl())
        val customerResult = customerClient.getCustomer("1")

        // that that customer is found
        assertThat(customerResult.isRight).isTrue()

        // test all fields
        customerResult.map {customer ->
            TestUtil.testAllfieldsHaveValues(Customer::class.java, customer)
        }
    }
}

Voorbeeld unit test voor een call die een 500 teruggeeft:

import CustomerClient
import au.com.dius.pact.consumer.MockServer
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.core.model.RequestResponsePact
import au.com.dius.pact.core.model.annotations.Pact
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.io.IOException

@ExtendWith(PactConsumerTestExt::class)
@PactTestFor(providerName = "sample-service-2")
class FailureTest {

    @Pact(provider = "sample-service-2", consumer = "sample-service-1")
    fun createPact(builder: PactDslWithProvider): RequestResponsePact {
        return builder
            .given("default", mapOf("failure" to true))
            .uponReceiving("a request a customer when database is down")
            .path("/1")
            .method("GET")
            .willRespondWith()
            .status(500)
            .toPact()
    }

    @Test
    @Throws(IOException::class)
    fun testContract(mockServer: MockServer) {
        val customerClient = CustomerClient(mockServer.getUrl())
        val customerResult = customerClient.getCustomer("1")

        // that that customer is not found
        assertThat(customerResult.isRight).isFalse()

        // test failure response
        customerResult.mapLeft { errorMessage ->
            assertThat(errorMessage).isNotEmpty()
        }
    }

}

Voorbeeld unit test voor een call die een 404 teruggeeft:

import CustomerClient
import au.com.dius.pact.consumer.MockServer
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.core.model.RequestResponsePact
import au.com.dius.pact.core.model.annotations.Pact
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.io.IOException

@ExtendWith(PactConsumerTestExt::class)
@PactTestFor(providerName = "sample-service-2")
class NotFoundTest {

    @Pact(provider = "sample-service-2", consumer = "sample-service-1")
    fun createPact(builder: PactDslWithProvider): RequestResponsePact {
        return builder
            .given("default", mapOf("customerExists" to false))
            .uponReceiving("a request a customer that doesn't exists")
            .path("/1")
            .method("GET")
            .willRespondWith()
            .status(404)
            .toPact()
    }

    @Test
    @Throws(IOException::class)
    fun testContract(mockServer: MockServer) {
        val customerClient = CustomerClient(mockServer.getUrl())
        val customerResult = customerClient.getCustomer("1")

        // that that customer is not found
        assertThat(customerResult.isRight).isFalse()

        // test failure response
        customerResult.mapLeft { errorMessage ->
            assertThat(errorMessage).isEqualTo("Customer niet gevonden")
        }
    }

}

In de tests wordt gebruik gemaakt van een TestUtil die controleert of alle velden gevuld zijn:

import org.assertj.core.api.Assertions
import kotlin.reflect.jvm.kotlinProperty

object TestUtil {
    fun testAllfieldsHaveValues(clazz: Class<*>, objectInstance: Any?) {
        if (clazz.isEnum) return
        if (clazz.packageName.startsWith("java.lang")) return
        clazz.declaredFields.forEach {
            val packag = it.type.`package`
            if (packag != null && packag.name.contains("com.alliander")) {
                // complex type
                val returnType = it.type
                val value = it?.kotlinProperty?.getter?.call(objectInstance)
                Assertions.assertThat(value).`as`("Veld $it is leeg").isNotNull()
                testAllfieldsHaveValues(returnType, value)
            } else {
                // simple type, or collection
                val value = it?.kotlinProperty?.getter?.call(objectInstance)
                Assertions.assertThat(value).`as`("Veld $it is leeg").isNotNull()
                if (value is String) {
                    Assertions.assertThat(value).`as`("Veld $it is leeg").isNotEmpty()
                }
                if (value is Collection<*>) {
                    // collection
                    Assertions.assertThat(value).`as`("Veld $it heeft geen elementen").isNotEmpty()
                    val firstElement = value.elementAt(0)
                    testAllfieldsHaveValues(
                        firstElement!!.javaClass,
                        firstElement
                    )
                }
            }
        }
    }
}

2 gedachtes over “Contract testing met Pactflow (deel 2/3) : Consumer test

Reacties zijn gesloten.