Skip to content

Wrong PactVerifyProvider-annoated method invoked (on different superclass instance) #1890

@jmferland

Description

@jmferland

When writing provider tests for asynchronous pacts, I would like the @PactVerifyProvider-annotated methods on a provider test's superclass(es) to be called on the JUnit test instance, instead of a new test instance created by Pact. Only the JUnit test instance will have been properly set up by various extensions (e.g., spring dependency injection, mockito injection, etc.).

Currently, MessageTestTarget.prepareVerifier seems unnecessarily specific, only using the initialized JUnit test instance if it has the same class as the annotated method's class (m.declaringClass == testInstance.javaClass). However, the JUnit test instance can also be used if it is an instance of the annotated method's class (m.declaringClass.isInstance(testInstance)).

To demonstrate without Spring, when using pacts/some-consumer-some-provider.json, both ParentProviderTest and ChildProviderTest fail, although I would expect ChildProviderTest to succeed because it sets a compatible event. Uncommenting //context.target = fixedMessageTestTarget(packagesToScan) will make ChildProviderTest succeed, because it calls ChildProviderTest.anEvent() instead of ParentProviderTest.anEvent().

import au.com.dius.pact.core.model.Pact
import au.com.dius.pact.provider.IProviderVerifier
import au.com.dius.pact.provider.PactVerifyProvider
import au.com.dius.pact.provider.junit5.MessageTestTarget
import au.com.dius.pact.provider.junit5.PactVerificationContext
import au.com.dius.pact.provider.junit5.PactVerificationInvocationContextProvider
import au.com.dius.pact.provider.junitsupport.IgnoreMissingStateChange
import au.com.dius.pact.provider.junitsupport.Provider
import au.com.dius.pact.provider.junitsupport.loader.PactFolder
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.TestTemplate
import org.junit.jupiter.api.extension.ExtendWith
import java.util.function.Function

@Provider("some-provider")
@PactFolder("pacts")
@IgnoreMissingStateChange
open class ParentProviderTest() {
    var event: String = """{"id":"invalid-uuid"}"""

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider::class)
    fun testTemplate(context: PactVerificationContext) {
        context.verifyInteraction()
    }

    class ChildProviderTest : ParentProviderTest() {
        init {
            event = """{"id":"12341234-1234-1234-1234-123412341234"}"""
        }
    }

    @BeforeEach
    fun beforeEach(context: PactVerificationContext) {
        val packagesToScan = listOf(ParentProviderTest::class.java.packageName)
        context.target = MessageTestTarget(packagesToScan)
        //context.target = fixedMessageTestTarget(packagesToScan)
    }

    private fun fixedMessageTestTarget(packagesToScan: List<String>): MessageTestTarget = object : MessageTestTarget(packagesToScan) {
        override fun prepareVerifier(
            verifier: IProviderVerifier,
            testInstance: Any,
            pact: Pact
        ) {
            super.prepareVerifier(verifier, testInstance, pact)
            val defaultProviderMethodInstance = verifier.providerMethodInstance
            verifier.providerMethodInstance = Function { m ->
                if (m.declaringClass.isInstance(testInstance)) {
                    testInstance
                } else {
                    defaultProviderMethodInstance.apply(m)
                }
            }
        }
    }

    @PactVerifyProvider("an event")
    open fun anEvent() = event
}

pacts/some-consumer-some-provider.json:

{
  "consumer": {
    "name": "some-consumer"
  },
  "interactions": [
    {
      "comments": {
        "testname": "repro.ReproConsumerTest.canHandleEvent(List)"
      },
      "contents": {
        "content": {
          "id": "e2490de5-5bd3-43d5-b7c4-526e33f71304"
        },
        "contentType": "application/json",
        "encoded": false
      },
      "description": "an event",
      "generators": {
        "body": {
          "$.id": {
            "type": "Uuid"
          }
        }
      },
      "key": "499aa24f",
      "matchingRules": {
        "body": {
          "$.id": {
            "combine": "AND",
            "matchers": [
              {
                "match": "regex",
                "regex": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
              }
            ]
          }
        }
      },
      "metadata": {
        "contentType": "application/json"
      },
      "pending": false,
      "providerStates": [
        {
          "name": "none"
        }
      ],
      "type": "Asynchronous/Messages"
    }
  ],
  "metadata": {
    "pact-jvm": {
      "version": "4.6.18"
    },
    "pactSpecification": {
      "version": "4.0"
    },
    "plugins": [

    ]
  },
  "provider": {
    "name": "some-provider"
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions