Enterprise Java

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:

Get the example project

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Datta Sai
Datta Sai
3 years ago

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

Back to top button