pact-foundation / pact-jvm

JVM version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://docs.pact.io
Apache License 2.0
1.08k stars 480 forks source link

Make CI/CD integration of Maven Plugin easier #1412

Closed JapuDCret closed 2 years ago

JapuDCret commented 3 years ago

Issue

The Pact Maven Plugin currently requires that the service, that the providers should be verified against, is available prior to starting the maven build/goal. However, when you want to verify a new version on a build pipeline, you (usually) do not have an instance of that version which you can test against; it must be started right there and then.

This is hindered by the current implementation of the Maven Plugin, because

  1. the needed infrastructure (so the service itself and everything that it needs) cannot be started with just the Pact Maven Plugin
  2. the Pact Maven Plugin does not (easily) allow for setting the values for the host/port of a service provider dynamically

This issue is linked to this Slack Thread.

Expected behavior

The Pact Maven Plugins allows for easier integration of the necessary infrastructure (like the Gradle plugin).

Solutions

Possible solutions

IMO This issue can be fixed in three ways

  1. Add the ability to startup the necessary infrastructure to validate a provider (e.g. starting up testcontainers)
  2. Add the ability to dynamically set the provider host/port
  3. Do not fix; recommend the workaround (or something similar)

My current workaround

I'm starting the required infrastructure via the exec-maven-plugin as testcontainers and make the dynamic host/port available as system properties. Then I add a requestFilter to each provider that uses the REQUEST_RESPONSE verification type (i.e. REST APIs), where the request uri is changed to the previously set values. Both the infrastructure startup and the pact provider verification are done in the maven phase post-integration-test.

This here is the crucial part for the workaround:

<build>
    <plugins>
        <!-- Startup infrastructure for Pact REST provider tests -->
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>startup-pact-infrastructure</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>java</goal>
                    </goals>
                    <configuration>
                        <classpathScope>test</classpathScope>
                        <cleanupDaemonThreads>false</cleanupDaemonThreads>
                        <mainClass>com.example.pact.PrePactProviderVerificationSetup</mainClass>
                    </configuration>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>au.com.dius.pact.provider</groupId>
            <artifactId>maven</artifactId>
            <configuration>
                <!-- pact config -->
                <serviceProviders>
                    <serviceProvider>
                        <name>MyProvider</name>
                        <protocol>http</protocol>
                        <requestFilter>
                            def host = System.properties['test.service.host'] ?: 'localhost'
                            def port = System.properties['test.service.port'] ?: '8080'
                            def path = request.getURI().getPath()
                            def query = request.getURI().getQuery()

                            request.setURI(URI.create("http://$host:$port$path" + (query ? "?$query" : '')))
                        </requestFilter>
                    </serviceProvider>
                </serviceProviders>
            </configuration>
            <executions>
                <!-- Publish contracts after Unit tests -->
                <execution>
                    <id>publish-pact-contracts</id>
                    <phase>test</phase>
                    <goals>
                        <goal>publish</goal>
                    </goals>
                </execution>
                <!-- Verify providers after integration tests -->
                <execution>
                    <id>verify-pact-contracts</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
uglyog commented 3 years ago

I have discovered with Maven that you can use system properties in your POM file: https://maven.apache.org/pom.html#properties

So you could use something like:

                        <name>MyProvider</name>
                        <protocol>http</protocol>
                        <host>${test.service.host}</host>
                        <port>${test.service.port}</port>
                    </serviceProvider>
JapuDCret commented 3 years ago

Yes this does work, but only when the system properties are available during the evaluation of the POM. They are not evaluated, when the Plugin is executed and therefore you cannot use a system property, that was set in a previous plugin/phase. (That's why I used the Groovy script)

uglyog commented 3 years ago

Pact-JVM has the ability to parse expressions, so if we can pass in ${test.service.host} for the host, I can set it to resolve that when the plugin runs. But I don't know how to stop Maven from replacing the value when the POM is loaded.

JapuDCret commented 3 years ago

Maybe your idea with expression parsing could be solved, with the fix for #1410 (changing the prefix and suffix of the expressions)

uglyog commented 3 years ago

You can now use

    <host>{{pact.host}}</host>
    <port>{{pact.port}}</port>

and that will look for the JVM system properties pact.host and pact.port when the verify task is run

uglyog commented 3 years ago

4.2.11 released