Hi, i’m writing an async consumer test in kotling ...
# pact-jvm
j
Hi, i’m writing an async consumer test in kotling and having the following error. Not sure what if i am missing something.
Copy code
null cannot be cast to non-null type au.com.dius.pact.core.model.BasePact
java.lang.NullPointerException: null cannot be cast to non-null type au.com.dius.pact.core.model.BasePact
	at au.com.dius.pact.consumer.junit5.PactConsumerTestExt.storePactForWrite(PactConsumerTestExt.kt:478)
	at au.com.dius.pact.consumer.junit5.PactConsumerTestExt.afterTestExecution(PactConsumerTestExt.kt:459)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAfterTestExecutionCallbacks$9(TestMethodTestDescriptor.java:233)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$13(TestMethodTestDescriptor.java:273)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeAllAfterMethodsOrCallbacks$14(TestMethodTestDescriptor.java:273)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAllAfterMethodsOrCallbacks(TestMethodTestDescriptor.java:272)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeAfterTestExecutionCallbacks(TestMethodTestDescriptor.java:232)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
	at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy5.stop(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
	at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
	at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
	at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
u
Are you able to provide the test? The failure is that the Pact object it is trying to store is null
j
sure,
Copy code
@file:OptIn(ExperimentalCoroutinesApi::class)

package io.travelx.flights.itineraryreservations.cdc

import arrow.core.left
import arrow.core.right
import au.com.dius.pact.consumer.MessagePactBuilder
import au.com.dius.pact.consumer.dsl.LambdaDsl.newJsonBody
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.consumer.junit5.ProviderType
import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.annotations.Pact
import au.com.dius.pact.core.model.annotations.PactDirectory
import au.com.dius.pact.core.model.messaging.MessagePact
import io.kotest.assertions.arrow.core.shouldBeRight
import io.ktor.client.engine.okhttp.*
import io.mockk.coEvery
import io.mockk.mockk
import io.travelx.flights.itineraryreservations.TestCalendar
import io.travelx.flights.itineraryreservations.core.domain.UnexpectedError
import io.travelx.flights.itineraryreservations.core.domain.commands.CreateReservationCommand
import io.travelx.flights.itineraryreservations.core.domain.model.CreateReservationData
import io.travelx.flights.itineraryreservations.core.usecases.reservation.cancel.CancelPurchaseUseCase
import io.travelx.flights.itineraryreservations.core.usecases.reservation.confirm.ConfirmReservationUseCase
import io.travelx.flights.itineraryreservations.core.usecases.reservation.create.CreateReservationUseCase
import io.travelx.flights.itineraryreservations.infrastructure.messaging.CheckoutSQSMessageProcessor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import software.amazon.awssdk.services.sqs.model.Message
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue

@ExtendWith(value = [PactConsumerTestExt::class])
@PactTestFor(providerName = "checkout-service", providerType = ProviderType.ASYNCH, pactVersion = PactSpecVersion.V4)
@PactDirectory("pacts")
class ReservationConsumerContractTest {

    private val jsonBody = newJsonBody { o ->
        o.stringType("purchaseId", "111")
        o.stringType("name", "PURCHASE_STARTED")
        o.eachLike("products", 1) { items ->
            items.stringType("productID", "1")
            items.stringType("productType", "FLIGHT")
            items.stringType(
                "availabilityId",
                "RkxZfEVaRV9JR1IoRk98Rk98NTEwMClZXzIwMjItMDktMTJfMDc6MjBfS19LT1dfMEBhPTF8TkVU"
            )
        }
    }.build()

    @Pact(consumer = "reservation-service", provider = "checkout-service")
    fun pactForReservationBooking(builder: MessagePactBuilder): MessagePact {
        return builder
            .hasPactWith("checkout-service")
            .expectsToReceive("a purchase started message to book a reservation")
            .withContent(jsonBody)
            .toPact()
    }

    @Test
    @PactTestFor(pactMethod = "pactForReservationBooking")
    fun testPactForReservationBooking() = runTest {
        val createReservationUseCaseStub = mockk<CreateReservationUseCase>()
        val confirmReservationUseCaseStub = mockk<ConfirmReservationUseCase>()
        val cancelPurchaseUseCaseStub = mockk<CancelPurchaseUseCase>()

        coEvery {
            confirmReservationUseCaseStub.invoke(any())
        } returns UnexpectedError(IllegalStateException("Confirm should not be called")).left()

        coEvery {
            cancelPurchaseUseCaseStub.invoke(any())
        } returns UnexpectedError(IllegalStateException("Cancel should not be called")).left()

        coEvery {
            createReservationUseCaseStub.invoke(
                CreateReservationCommand(
                    CreateReservationData(
                        "111",
                        "RkxZfEVaRV9JR1IoRk98Rk98NTEwMClZXzIwMjItMDgtMjlfMDY6MzBfS19LT1dfMEBhPTF8TkVU",
                        "Bearer token",
                    ),
                    createdAt = TestCalendar.instant(),
                )
            )
        } returns Unit.right()

        val sut = CheckoutSQSMessageProcessor(
            createReservationUseCaseStub,
            confirmReservationUseCaseStub,
            cancelPurchaseUseCaseStub,
            TestCalendar,
        )
        val response = sut.invoke(
            Message.builder()
                .body(
                    """
                    {"purchaseId": "111",
        "name": "PURCHASE_STARTED",
        "products" : [
        {
            "productId": "1",
            "availabilityId": "RkxZfEVaRV9JR1IoRk98Rk98NTEwMClZXzIwMjItMDgtMjlfMDY6MzBfS19LT1dfMEBhPTF8TkVU",
            "productType": "FLIGHT"
        }]}"""
                ).messageId("1IoRk98Rk98NTEwMClZ")
                .messageAttributes(
                    mapOf(
                        "Authorization" to MessageAttributeValue.builder().stringValue("Bearer token").build(),
                        "messageDeduplicationId" to MessageAttributeValue.builder().stringValue("1").build(),
                        "messageGroupId" to MessageAttributeValue.builder().stringValue("10").build()
                    )
                ).build()
        )

        response shouldBeRight Unit
    }
}
any tipp?
u
I can't see anything wrong. I'll need to setup a Kotlin test to see if I can replicate it
j
i was able to make it works with pactV3:
Copy code
@file:OptIn(ExperimentalCoroutinesApi::class)

package io.travelx.flights.itineraryreservations.cdc

import arrow.core.left
import arrow.core.right
import au.com.dius.pact.consumer.MessagePactBuilder
import au.com.dius.pact.consumer.dsl.LambdaDsl.newJsonBody
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.consumer.junit5.ProviderType
import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.annotations.Pact
import au.com.dius.pact.core.model.annotations.PactDirectory
import au.com.dius.pact.core.model.messaging.MessagePact
import io.kotest.assertions.arrow.core.shouldBeRight
import io.ktor.client.engine.okhttp.*
import io.mockk.coEvery
import io.mockk.mockk
import io.travelx.flights.itineraryreservations.TestCalendar
import io.travelx.flights.itineraryreservations.core.domain.UnexpectedError
import io.travelx.flights.itineraryreservations.core.domain.commands.CreateReservationCommand
import io.travelx.flights.itineraryreservations.core.domain.model.CreateReservationData
import io.travelx.flights.itineraryreservations.core.usecases.reservation.cancel.CancelPurchaseUseCase
import io.travelx.flights.itineraryreservations.core.usecases.reservation.confirm.ConfirmReservationUseCase
import io.travelx.flights.itineraryreservations.core.usecases.reservation.create.CreateReservationUseCase
import io.travelx.flights.itineraryreservations.infrastructure.messaging.CheckoutSQSMessageProcessor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import software.amazon.awssdk.services.sqs.model.Message
import software.amazon.awssdk.services.sqs.model.MessageAttributeValue

@ExtendWith(value = [PactConsumerTestExt::class])
@PactTestFor(providerName = "checkout-service", providerType = ProviderType.ASYNCH, pactVersion = PactSpecVersion.V3)
@PactDirectory("pacts")
class ReservationConsumerContractTest {

    private val checkoutCompletedJsonBody = newJsonBody { o ->
        o.stringType("purchaseId", "111")
        o.stringType("name", "PURCHASE_STARTED")
        o.eachLike("products", 1) { items ->
            items.stringType(
                "productId",
                "29e44fee-66e6-48c9-950e-3d84bbc675d7"
            )

        }
    }.build()

    private val purchaseStartedJsonBody = newJsonBody { o ->
        o.stringType("purchaseId", "111")
        o.stringType("name", "PURCHASE_STARTED")
        o.eachLike("products", 1) { items ->
            items.stringType(
                "availabilityId",
                "RkxZfEVaRV9JR1IoRk98Rk98NTEwMClZXzIwMjItMDktMTJfMDc6MjBfS19LT1dfMEBhPTF8TkVU"
            )
            items.stringType(
                "productId",
                ""
            )
            items.stringType(
                "productType",
                "FLIGHT"
            )
        }
    }.build()

    @Pact(consumer = "reservation-service", provider = "checkout-service")
    fun pactForReservationBooking(builder: MessagePactBuilder): MessagePact {
        return builder
            .hasPactWith("checkout-service")
            .expectsToReceive("a purchase started message to book a reservation")
            .withContent(purchaseStartedJsonBody)
            .toPact()
    }

    @Test
    @PactTestFor(pactMethod = "pactForReservationBooking")
    fun testPactForReservationBooking(messages: List<au.com.dius.pact.core.model.messaging.Message>) = runTest {
        val createReservationUseCaseStub = mockk<CreateReservationUseCase>()
        val confirmReservationUseCaseStub = mockk<ConfirmReservationUseCase>()
        val cancelPurchaseUseCaseStub = mockk<CancelPurchaseUseCase>()

        coEvery {
            confirmReservationUseCaseStub.invoke(any())
        } returns UnexpectedError(IllegalStateException("Confirm should not be called")).left()

        coEvery {
            cancelPurchaseUseCaseStub.invoke(any())
        } returns UnexpectedError(IllegalStateException("Cancel should not be called")).left()

        coEvery {
            createReservationUseCaseStub.invoke(
                CreateReservationCommand(
                    CreateReservationData(
                        "111",
                        "RkxZfEVaRV9JR1IoRk98Rk98NTEwMClZXzIwMjItMDktMTJfMDc6MjBfS19LT1dfMEBhPTF8TkVU",
                        "Bearer token",
                    ),
                    createdAt = TestCalendar.instant(),
                )
            )
        } returns Unit.right()

        val sut = CheckoutSQSMessageProcessor(
            createReservationUseCaseStub,
            confirmReservationUseCaseStub,
            cancelPurchaseUseCaseStub,
            TestCalendar,
        )

        for (message in messages) {
            val response = sut.invoke(
                Message.builder()
                    .body(message.contentsAsString())
                    .messageId("1IoRk98Rk98NTEwMClZ")
                    .messageAttributes(
                        mapOf(
                            "Authorization" to MessageAttributeValue.builder().stringValue("Bearer token").build(),
                            "messageDeduplicationId" to MessageAttributeValue.builder().stringValue("1").build(),
                            "messageGroupId" to MessageAttributeValue.builder().stringValue("10").build()
                        )
                    ).build()
            )

            response shouldBeRight Unit
        }
    }
}
u
I think this was the problem:
Copy code
@Pact(consumer = "reservation-service", provider = "checkout-service")
fun pactForReservationBooking(builder: MessagePactBuilder): MessagePact
It is not using the new V4 classes (should return a V4Pact)
j
mm.. i have replaced MessagePact with V4Pact, however the result is the same. I’ am not sure if
Copy code
fun testPactForReservationBooking(messages: List<au.com.dius.pact.core.model.messaging.Message>)
is required, i have seen examples written in different ways. Some create the message by itself and other let pact create them. I’m finding a bit difficult to understand the documentation.