Pact part 2: Sharing contracts with the Pact Broker

My last article covered my first steps with using Pact to implement Consumer Driven Contracts. One thing that I left out intentionally was working with the Pact Broker to share contracts between consumers and providers. This post looks at just that. I will show how to locally run a Pact Broker, how consumers can publish contracts to that broker and how providers can retrieve and verify contracts that are of concern to them. I will provide code samples inline, but you can also have a look at my complete sample project on Codeberg.

It might make sense for you to read part one of my pact posts first, as my examples below build on it.

Running a Pact Broker locally

There are different ways how you can work with a broker. If you don’t want to set up your own broker, you might want to register with pactflow.io, they provide a free starter plan that will probably be enough for trying things out. I have not looked at it, though.

You can also run a broker using docker and this is the way I went. There are two different images that you can choose from: pactfoundation/pact-broker and dius/pact-broker. The first one is a forked version of the latter, it’s supposed to be smaller and more performant. For our use case it doesn’t matter much what we choose, I went with the pactfoundation image. If you need more information on which one you should choose, the docker hub page of the pactfoundation image has a section on that.

The pact broker requires a postgres database to work, so it’s convenient to use docker compose to spin up and configure both containers. Here’s my docker compose file, it’s a slightly adapted version of the one provided by pactfoundation here.

version: "3"
services:
postgres:
image: postgres
healthcheck:
test: psql postgres --command "select 1" -U postgres
volumes:
- postgres-volume:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: postgres
pact-broker:
image: pactfoundation/pact-broker:latest
ports:
- "9292:9292"
depends_on:
- postgres
environment:
PACT_BROKER_PORT: '9292'
PACT_BROKER_DATABASE_URL: "postgres://postgres:password@postgres/postgres"
PACT_BROKER_LOG_LEVEL: INFO
PACT_BROKER_SQL_LOG_LEVEL: DEBUG
PACT_BROKER_DATABASE_CONNECT_MAX_RETRIES: "5"
PACT_BROKER_BASE_URL: 'https://localhost http://localhost http://localhost:9292 http://pact-broker:9292 https://host.docker.internal http://host.docker.internal http://host.docker.internal:9292'
volumes:
postgres-volume:

That’s it, now you can run docker-compose up and your pact broker is at your service. Go to a browser and visit http://localhost:9292/ to open the broker web UI.

Versioning

Before we move on to actually publishing and verifying contracts, we need to talk about versioning. In order to get reliable results from our tests, there are different versions that need to be considered. First, the contracts themselves can have different versions. We are not concerned with that, as the pact broker handles these transparentely. Consumers and providers can also have different versions, though. Every time we publish a contract, we need to tell the broker which consumer version published a contract. When a provider verifies a contract, it’s also important to know which version of a provider verified or didn’t verify a contract. Have a look at the documentation for an elaborate explanation. In the following examples I use fake or default versions, but I will show you how you can set and override versions.

Publishing contracts

With the pact broker running, let’s now look at how consumers can publish contracts. Again, there’s different ways to go about it. Of course there’s a section in the official documentation for it and it helps to understand some basics, but in the end, it didn’t deliver a good enough solution for my taste and misses some options.

The documentation recommends using the official CLI tools that are provided either through a docker file or a standalone executable. As an alternative you can also publish by HTTP requests using the broker API. However, there’s also the possibility to use the Gradle plugin or the Maven plugin to do that. Since my project is already setup using Gradle as a build tool, for me this was the easiest way to publish contracts to the broker. Depending on your use case this might not be true for you, so I will show how to publish using both the CLI and the Gradle plugin. The maven approach should be very similar, but I didn’t try it myself.

Publishing with the CLI

I’m using the dockerized CLI version here, there’s some guidance on how to use it on its docker hub page. If you work with pactflow.io as your broker, you will need to take care of authentication as shown on docker hub, but our broker runs locally and we can just skip authentication. On the other hand, we need to make the CLI inside the docker container communicate with the pact broker that lives inside another docker container. Since its TCP port is exposed to the docker host, we can still connect the CLI using the special DNS name host.docker.internal provided by docker. We’ll also need to specify the correct tcp port, of course.

docker run --rm \
 -w ${PWD} \
 -v ${PWD}:${PWD} \
 -e host.docker.internal:9292 \
  pactfoundation/pact-cli:latest \
  publish \
  ${PWD}/build/pacts \
  --consumer-app-version fake-git-sha-for-demo-$(date +%s) \
  --tag-with-git-branch

Looking at the broker UI, you now see a published contract.

Screenshot of the pact broker UI showing a published contract of the consumer "UserConsumer" for the provider "UserServiceJUnit5". Additional information shows it has not yet been verified.

If you look closely, you will see that the contract has not been verified, though. We’ll get to that later.

Publishing with the Gradle plugin

As mentioned above, you can also use the Pact Gradle plugin to publish your contracts. This is documented but not very visible in the official documentation pages. You might want to delete previously published contracts, so that you can see the publishing working successfully.

There’s not a lot to configure to get this running. First, add the gradle plugin to your build file.

id "au.com.dius.pact" version "4.4.2"

Afterwards, we need to specify the broker URL in the plugin’s configuration. If you have set up a local version, the configuration looks like this:

pact {
broker {
pactBrokerUrl = "http://localhost:9292"
}
}
view raw gradle.groovy hosted with ❤ by GitHub

In contrast to the official documentation, I’m specifying the url in the broker section of the configuration, not in the publish section. I’m doing this, because it will also be used in a possible verification task. The publish section still needs to be there, even it is empty.

If you’re using the pactflow.io broker, you will also have to add authentication information to your configuration. For further details, have a look here .

There’s other configuration options that let you specify the location of the generated contract files, exclude specific contract files, and more. Consult the documentation, for that.

Now you can run the following Gradle task to publish your contracts:

gradle pactPublish

Looking at the broker UI again you should see the the published contract.

By default the Gradle plugin will use the artifact version as the consumer contract version. In my case this is 0.0.1-SNAPSHOT. You can override the version directly in the plugin confirguration by specifying the consumerVersion property in the publish section or by using either a JVM system property or an environment variable.

Publishing with the Maven plugin

If you’re a Maven person, the configuration for the Maven plugin is very similar. Here’s the corresponding documentation. Correspondingly you’ll publish your contracts by running:

mvn pact:publish

Retrieving and verifying contracts

Now that we have successfully published our contracts to the broker, let’s integrateretrieve and verify contracts. There are different ways to do that, let’s look at two of them.

Plain JUnit5 provider tests

Previously, we have seen a JUnit5 provider test reading from the local filesystem. We’ll build on it and connect it to the broker.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Provider("UserServiceJUnit5")
@PactBroker(url = "http://localhost:9292")
@SetSystemProperty(key = "pact.verifier.publishResults", value = "true")
public class PactProviderJUnit5WithBrokerTest {
@LocalServerPort
int port;
@BeforeEach
public void setup(PactVerificationContext context) {
context.setTarget(new HttpTestTarget("localhost", port));
}
@State("A running user service")
void setupUserService() {
// no state setup ATM
}
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
}

There’s a few things to point out here. First and most important, I’m using the @PactBroker annotation to specify the url of the broker. This is enough for pact to successfully retrieve contracts for the provider specified by the @Provider annotation.

There’s one more thing I added to the test, though. The results of a successful verification can be sent back to the broker. This will result in the contract showing up as verified in the broker UI. To do that, you need to set the system property pact.verifier.publishResults to true. There’s different ways to do this in our current test case. Here I have used the JUnit Pioneer extension @SystemProperty.

Screenshot of the broker UI. It shows a contract between the consumer "UserConsumer" and the provider "UserServiceJUnit5" and the contract is marked as verified "less than a minute ago".

Gradle plugin

As we have already set up the Gradle plugin to publish our contracts, lets have a look at how we can configure it to retrieve and verify contracts. Again, you might want to clean up in the broker before continuing.

We have already set up the broker url before, so Gradle already knows which broker to talk to. Now we need to add provider configuration to the build file. The official documentation for that is here, applying it to our example it will look like this.

pact {
publish {
// empty, but needs to be there
}
broker {
pactBrokerUrl = "http://localhost:9292"
}
serviceProviders {
UserServiceJUnit5 {
// empty, but needs to be there
}
}
}
view raw gradle.groovy hosted with ❤ by GitHub

Pact executes the contracts against the provider service. It defaults to http://localhost:8080, but we haven’t setup Gradle to take care of this. As a shortcut, we’ll manually start our application before invoking Gradle.

gradle pactVerify

If you want the verification to be reported back to the broker, you can set pact.verifier.publishResults as a gradle property on the command line or in the gradle.properties file.

You can do the same thing with the Maven plugin, of course. Have a look at the corresponding documentation.

Summary

I’ve shown a few ways how you can integrate with the Pact broker. I hope this can be helpful when starting out, but obviously it’s not directly adaptable to your production needs. Please let me know where you would do things differently and where I have gaps or errors. Thanks for reading, anyway!