From 4a6c7dff1ee58782536e487cc50375f40aa43c78 Mon Sep 17 00:00:00 2001 From: Ronald Holshausen Date: Sat, 8 May 2021 14:39:52 +1000 Subject: [PATCH] refactor: support multiple providerinfo in JUnit 5 tests #1342 --- .../consumer/junit5/PactConsumerTestExt.kt | 20 +++-- .../consumer/junit5/MultiProviderTest.groovy | 74 +++++++++++++++++++ .../pact/consumer/junit5/MultiTest.groovy | 1 - .../junit5/PactConsumerTestExtTest.groovy | 11 ++- .../consumer/junit5/xml/XMLPactTest.groovy | 58 +++++++-------- provider/gradle/README.md | 9 +-- 6 files changed, 125 insertions(+), 48 deletions(-) create mode 100644 consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiProviderTest.groovy diff --git a/consumer/junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt b/consumer/junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt index b8102472c4..506eb3d650 100644 --- a/consumer/junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt +++ b/consumer/junit5/src/main/kotlin/au/com/dius/pact/consumer/junit5/PactConsumerTestExt.kt @@ -111,7 +111,13 @@ annotation class PactTestFor( * The type of mock server implementation to use. The default is to use the Java server for HTTP and the KTor * server for HTTPS */ - val mockServerImplementation: MockServerImplementation = MockServerImplementation.Default + val mockServerImplementation: MockServerImplementation = MockServerImplementation.Default, + + /** + * Test methods that provides the Pacts to use for the test. This allows multiple providers to be + * used in the same test. + */ + val pactMethods: Array = [] ) data class ProviderInfo @JvmOverloads constructor( @@ -183,7 +189,7 @@ class JUnit5MockServerSupport(private val baseMockServer: BaseMockServer) : Abst class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCallback, ParameterResolver, AfterTestExecutionCallback, AfterAllCallback { override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { - val providerInfo = lookupProviderInfo(extensionContext).first + val providerInfo = lookupProviderInfo(extensionContext).first().first val type = parameterContext.parameter.type return when (providerInfo.providerType) { ProviderType.ASYNCH -> if (providerInfo.pactVersion == PactSpecVersion.V4) { @@ -208,7 +214,7 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal } override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { - val providerInfo = lookupProviderInfo(extensionContext) + val providerInfo = lookupProviderInfo(extensionContext).first() val store = extensionContext.getStore(NAMESPACE) val type = parameterContext.parameter.type return when (providerInfo.first.providerType) { @@ -246,7 +252,7 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal } override fun beforeTestExecution(context: ExtensionContext) { - val (providerInfo, pactMethod) = lookupProviderInfo(context) + val (providerInfo, pactMethod) = lookupProviderInfo(context).first() logger.debug { "providerInfo = $providerInfo" } if (providerInfo.providerType != ProviderType.ASYNCH) { @@ -270,10 +276,10 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal } } - fun lookupProviderInfo(context: ExtensionContext): Pair { + fun lookupProviderInfo(context: ExtensionContext): List> { val store = context.getStore(NAMESPACE) return if (store["providerInfo"] != null) { - (store["providerInfo"] as ProviderInfo) to store["pactMethod"].toString() + listOf((store["providerInfo"] as ProviderInfo) to store["pactMethod"].toString()) } else { val methodAnnotation = if (AnnotationSupport.isAnnotated(context.requiredTestMethod, PactTestFor::class.java)) { logger.debug { "Found @PactTestFor annotation on test method" } @@ -305,7 +311,7 @@ class PactConsumerTestExt : Extension, BeforeTestExecutionCallback, BeforeAllCal store.put("providerInfo", providerInfo.first) store.put("pactMethod", providerInfo.second) - providerInfo + listOf(providerInfo) } } diff --git a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiProviderTest.groovy b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiProviderTest.groovy new file mode 100644 index 0000000000..0edab94b3f --- /dev/null +++ b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiProviderTest.groovy @@ -0,0 +1,74 @@ +package au.com.dius.pact.consumer.junit5 + +import au.com.dius.pact.consumer.MockServer +import au.com.dius.pact.consumer.dsl.PactDslWithProvider +import au.com.dius.pact.core.model.RequestResponsePact +import au.com.dius.pact.core.model.annotations.Pact +import groovyx.net.http.FromServer +import groovyx.net.http.HttpBuilder +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +import static au.com.dius.pact.consumer.dsl.LambdaDsl.newJsonBody + +@SuppressWarnings('UnusedMethodParameter') +@ExtendWith(PactConsumerTestExt) +@Disabled +class MultiProviderTest { + + @Pact(provider = 'provider1', consumer= 'consumer') + RequestResponsePact pact1(PactDslWithProvider builder) { + builder + .uponReceiving('a new user') + .path('/some-service/users') + .method('POST') + .body(newJsonBody { + it.stringValue('name', 'bob') + }.build()) + .willRespondWith() + .status(201) + .matchHeader('Location', 'http(s)?://\\w+:\\d+//some-service/user/\\w{36}$') + .toPact() + } + + @Pact(provider = 'provider2', consumer= 'consumer') + RequestResponsePact pact2(PactDslWithProvider builder) { + builder + .uponReceiving('a new user') + .path('/some-service/users') + .method('POST') + .body(newJsonBody { + it.numberType('id') + }.build()) + .willRespondWith() + .status(204) + .toPact() + } + + @Test + @PactTestFor(pactMethods = ['pact1', 'pact2']) + void runTest1(MockServer mockServer1, MockServer mockServer2) { + def http = HttpBuilder.configure { request.uri = mockServer1.url } + + http.post { + request.uri.path = '/some-service/users' + request.body = user() + request.contentType = 'application/json' + + response.success { FromServer fs, Object body -> + assert fs.statusCode == 201 + assert fs.headers.find { it.key == 'Location' }?.value?.contains(SOME_SERVICE_USER) + } + } + + http.get { + request.uri.path = SOME_SERVICE_USER + EXPECTED_USER_ID + request.contentType = 'application/json' + response.success { FromServer fs, Object body -> + assert fs.statusCode == 200 + } + } + + } +} diff --git a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiTest.groovy b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiTest.groovy index 2236a5bcb3..0148c19efd 100644 --- a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiTest.groovy +++ b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/MultiTest.groovy @@ -167,5 +167,4 @@ class MultiTest { .status(404) .toPact() } - } diff --git a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy index 7e67db31cc..3698e65264 100644 --- a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy +++ b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/PactConsumerTestExtTest.groovy @@ -152,7 +152,7 @@ class PactConsumerTestExtTest { 'getTestMethod': { Optional.of(TestClass.methods.find { it.name == 'pactMethod' }) }, 'getStore': { mockStore } ] as ExtensionContext - def providerInfo = subject.lookupProviderInfo(context) + def providerInfo = subject.lookupProviderInfo(context)[0] assertThat(providerInfo.first.providerName, Matchers.is('')) assertThat(providerInfo.first.hostInterface, Matchers.is('')) assertThat(providerInfo.first.port, Matchers.is('')) @@ -170,7 +170,7 @@ class PactConsumerTestExtTest { 'getTestMethod': { Optional.of(TestClassWithClassLevelAnnotation.methods.find { it.name == 'pactMethod' }) }, 'getStore': { mockStore } ] as ExtensionContext - def providerInfo = subject.lookupProviderInfo(context) + def providerInfo = subject.lookupProviderInfo(context)[0] assertThat(providerInfo.first.providerName, Matchers.is('TestClassWithClassLevelAnnotation')) assertThat(providerInfo.first.hostInterface, Matchers.is('localhost')) assertThat(providerInfo.first.port, Matchers.is('8080')) @@ -188,7 +188,7 @@ class PactConsumerTestExtTest { 'getTestMethod': { Optional.of(TestClassWithMethodLevelAnnotation.methods.find { it.name == 'pactMethod' }) }, 'getStore': { mockStore } ] as ExtensionContext - def providerInfo = subject.lookupProviderInfo(context) + def providerInfo = subject.lookupProviderInfo(context)[0] assertThat(providerInfo.first.providerName, Matchers.is('TestClassWithMethodLevelAnnotation')) assertThat(providerInfo.first.hostInterface, Matchers.is('localhost')) assertThat(providerInfo.first.port, Matchers.is('8080')) @@ -208,7 +208,7 @@ class PactConsumerTestExtTest { }, 'getStore': { mockStore } ] as ExtensionContext - def providerInfo = subject.lookupProviderInfo(context) + def providerInfo = subject.lookupProviderInfo(context)[0] assertThat(providerInfo.first.providerName, Matchers.is('TestClassWithMethodAndClassLevelAnnotation')) assertThat(providerInfo.first.hostInterface, Matchers.is('testServer')) assertThat(providerInfo.first.port, Matchers.is('1234')) @@ -228,8 +228,7 @@ class PactConsumerTestExtTest { }, 'getStore': { mockStore } ] as ExtensionContext - def providerInfo = subject.lookupProviderInfo(context) + def providerInfo = subject.lookupProviderInfo(context)[0] assertThat(providerInfo.first.pactVersion, Matchers.is(PactSpecVersion.V3)) } - } diff --git a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/xml/XMLPactTest.groovy b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/xml/XMLPactTest.groovy index 985bfb030b..da9e85de90 100644 --- a/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/xml/XMLPactTest.groovy +++ b/consumer/junit5/src/test/groovy/au/com/dius/pact/consumer/junit5/xml/XMLPactTest.groovy @@ -24,44 +24,44 @@ class XMLPactTest { .uponReceiving('a POST request with an XML message') .method('POST') .path('/message') - .body(new PactXmlBuilder('Message').build(message -> { + .body(new PactXmlBuilder('Message').build { message -> message.setAttributes([type: 'Request']) - message.appendElement('Head', [:], head -> { - head.appendElement('Client', [name: 'WebCheck'], client -> { - client.appendElement('Version', regexp(/\d+\.\d+\.\d+\.\d+/, "2.2.8.2")) - }) - head.appendElement('Server', [:], server -> { - server.appendElement('Name', [:], "SrvCheck") - server.appendElement('Version', [:], "3.0") - }) - head.appendElement('Authentication', [:], authentication -> { - authentication.appendElement('User', [:], regexp(/\w+/, "user_name")) - authentication.appendElement('Password', [:], regexp(/\w+/, "password")) - }) + message.appendElement('Head', [:]) { head -> + head.appendElement('Client', [name: 'WebCheck']) { client -> + client.appendElement('Version', regexp(/\d+\.\d+\.\d+\.\d+/, '2.2.8.2')) + } + head.appendElement('Server', [:]) { server -> + server.appendElement('Name', [:], 'SrvCheck') + server.appendElement('Version', [:], '3.0') + } + head.appendElement('Authentication', [:]) { authentication -> + authentication.appendElement('User', [:], regexp(/\w+/, 'user_name')) + authentication.appendElement('Password', [:], regexp(/\w+/, 'password')) + } head.appendElement('Token', [:], '1234567323211242144') - }) - message.appendElement('Body', [:], body -> { - body.appendElement('Call', [method: 'getInfo', service: 'CheckRpcService'], call -> { - call.appendElement('Param', [name: regexp(/exportId|mtpId/, 'exportId')], param -> { + } + message.appendElement('Body', [:]) { body -> + body.appendElement('Call', [method: 'getInfo', service: 'CheckRpcService']) { call -> + call.appendElement('Param', [name: regexp(/exportId|mtpId/, 'exportId')]) { param -> param.appendElement('ExportId', regexp(/\d+/, '1234567890')) - }) - }) - }) - })) + } + } + } + }) .willRespondWith() .status(200) - .body(new PactXmlBuilder("Message").build(message -> { - message.appendElement('Head', [:], head -> { - head.appendElement('Server', [:], server -> { + .body(new PactXmlBuilder('Message').build { message -> + message.appendElement('Head', [:]) { head -> + head.appendElement('Server', [:]) { server -> server.appendElement('Name', [:], regexp(/\w+/, 'server_name')) server.appendElement('Version', [:], regexp(/.+/, 'server_version')) server.appendElement('Timestamp', [:], regexp(/.+/, 'server_timestamp')) - }) - }) - message.appendElement('Body', [:], body -> { + } + } + message.appendElement('Body', [:]) { body -> body.appendElement('Result', [state: 'SUCCESS']) - }) - })) + } + }) .toPact() } diff --git a/provider/gradle/README.md b/provider/gradle/README.md index 05a1e599f5..51c8faef2f 100644 --- a/provider/gradle/README.md +++ b/provider/gradle/README.md @@ -642,16 +642,15 @@ Preemptive Authentication can be enabled by setting the `pact.pactbroker.httpcli ### Allowing just the changed pact specified in a webhook to be verified [4.0.6+] When a consumer publishes a new version of a pact file, the Pact broker can fire off a webhook with the URL of the changed -pact file. To allow only the changed pact file to be verified, you can override the URL by using the `pact.filter.consumers` -and `pact.filter.pacturl` project properties. +pact file. To allow only the changed pact file to be verified, you can override the URL by using the `pact.filter.pacturl` project properties. For example, running: ```console -gradle pactVerify -Ppact.filter.consumers='Foo Web Client' -Ppact.filter.pacturl=https://test.pact.dius.com.au/pacts/provider/Activity%20Service/consumer/Foo%20Web%20Client/version/1.0.1 -``` +gradle pactVerify -Ppact.filter.pacturl=https://test.pact.dius.com.au/pacts/provider/Activity%20Service/consumer/Foo%20Web%20Client/version/1.0.1 +``` -will only run the verification for Foo Web Client with the given pact file URL. +will only run the verification with the given pact file URL. ## Verifying pact files from a S3 bucket