Consumer Driven Testing with Pact & Spring Boot
Recently a colleague of mine stumbled across Pact.io, Our current application had grown to over 50 services and we we’re starting to have some integration test failures and a brittle dev / acceptance test environment. So we decided to have a look at ways to try help with this.
I started out by reading: https://docs.pact.io/faq/convinceme.html
Then watching: https://www.youtube.com/watch?v=-6x6XBDf9sQ&feature=youtu.be
Those 2 resources convinced me to give it a shot.
So I set out and created a quick set of Spring boot apps, the GitHub repo here, to test out the concepts and get everything working.
To highlight some important bits from the demo.
Consumer:
As Pact is a consumer driven test framework. This is where you define a unit test, that test mocks the http server response and you assert against that.
Once the test is successful it creates a pact json file in the /pacts directory.
public class TestProvider {
@Rule
public PactProviderRule provider = new PactProviderRule("test_provider", "localhost", 8081, this);
@Pact(state = "default", provider = "test_provider", consumer = "test_consumer")
public PactFragment createFragment(PactDslWithProvider builder) {
Map<String, String> headers = new HashMap<>();
headers.put("content-type", "application/json");
return builder
.given("default")
.uponReceiving("Test User Service")
.path("/user/1")
.method("GET")
.willRespondWith()
.status(200)
.headers(headers)
.body("{" +
" \"userName\": \"Bob\",\n" +
" \"userId\": \"1\",\n" +
" \"firstName\": null,\n" +
" \"lastName\": null,\n" +
" \"email\": null,\n" +
" \"groups\": null\n" +
"}")
.toFragment();
}
@Test
@PactVerification("test_provider")
public void runTest() throws IOException {
final RestTemplate call = new RestTemplate();
final User expectedResponse = new User();
expectedResponse.setUserName("Bob");
expectedResponse.setUserId("1");
final User forEntity = call.getForObject(provider.getConfig().url() + "/user/1", User.class);
assertThat(forEntity, sameBeanAs(expectedResponse));
}
}So after the “mock” test is run and the pact file has been created. You need to include a maven plugin …pact… that is then used to publish the content of the pacts/ folder to the pact broker… which is defined in the pom as below.
<dependencies>
<!-- https://mvnrepository.com/artifact/au.com.dius/pact-jvm-consumer-junit_2.11 -->
<dependency>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-consumer-junit_2.11</artifactId>
<version>3.3.6</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18</version>
<configuration>
<systemPropertyVariables>
<pact.rootDir>pacts</pact.rootDir>
<buildDirectory>${project.build.directory}</buildDirectory>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-maven_2.11</artifactId>
<version>3.3.4</version>
<configuration>
<pactDirectory>pacts</pactDirectory>
<pactBrokerUrl>http://localhost:80</pactBrokerUrl>
<projectVersion>1.0.1</projectVersion>
</configuration>
</plugin>
</plugins>
</build>Producer:
This uses the JUnit integration from Pact.io to download the pacts from the broker and then run against an running service.
Since this already uses a @RunWith annotation, I could not use the spring boot runner. So to get around that as a before class step, I start the Spring boot application, the pacts then gets run against that running instance… and the boot application gets stopped again after the tests. Depending on your use case I guess it would also be an option to do this with @Before so you get a new service instance started before each pack, but that would slow down the execution tremendously.
The @State annotation, allows for clients to define a specific state, which the producer can the use to setup additional data / conditions required for the test to run.
Once the pacts have executed against the service there are reports generated in the target folder.
@RunWith(PactRunner.class)
@Provider("test_provider" )
@PactBroker(host = "localhost", port = "80")
@VerificationReports({"console", "markdown"})
public class TestPacts {
private static ConfigurableApplicationContext application;
@TestTarget
public final Target target = new HttpTarget(8080);
@BeforeClass
public static void startSpring(){
application = SpringApplication.run(ProviderServiceApplication.class);
}
@State("default")
public void toDefaultState() {
System.out.println("Now service in default state");
}
@State("extra")
public void toExtraState() {
System.out.println("Now service in extra state");
}
@AfterClass
public static void kill(){
application.stop();
}
}Setting up the Pact Broker
1. Grab the public images from Docker Hub.
docker pull dius/pact_broker docker pull postgres
2. Then setup the Postgres DB
docker run --name pactbroker-db -e POSTGRES_PASSWORD=ThePostgresPassword -e POSTGRES_USER=admin -d postgres docker run -it --link pactbroker-db:postgres --rm postgres psql -h postgres -U admin CREATE USER pactbrokeruser WITH PASSWORD 'TheUserPassword'; CREATE DATABASE pactbroker WITH OWNER pactbrokeruser; GRANT ALL PRIVILEGES ON DATABASE pactbroker TO pactbrokeruser;
3. Once the DB is up, run the actual Broker:
docker run --name pactbroker --link pactbroker-db:postgres -e PACT_BROKER_DATABASE_USERNAME=pactbrokeruser -e PACT_BROKER_DATABASE_PASSWORD=TheUserPassword -e PACT_BROKER_DATABASE_HOST=postgres -e PACT_BROKER_DATABASE_NAME=pactbroker -d -p 80:80 dius/pact_broker
Extra References:
- https://docs.pact.io/documentation/
- https://docs.pact.io/documentation/sharings_pacts.html
- https://github.com/DiUS/pact-jvm
- https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-junit
Get the example project
| Reference: | Consumer Driven Testing with Pact & Spring Boot from our JCG partner Brian Du Preez at the Zen in the art of IT blog. |





Hi
I have a query that for doing the PACT Consumer driven contract testing we have to STUB the API in our local machine and then test it