Consumer Tests with Pact, Junit5, SpringBoot
Do you set your house on fire to test your smoke alarm?…. https://docs.pact.io
A Consumer creates the consumer tests which confirms the contract between the consumer and a provider it is dependant on.
Pact is consumer driven, it is generated a part of the consumers tests and run by the producer.
The Pact framework creates a pact file based on interactions defined by the consumer. This is then used to verify the provider of the service interactions, the provider needs to pass the tests based on the file from the consumer before it is deemed to be verified.
This verification is provided by the Pact framework, and allows independent testing of the consumer and provider without having to integrate both together. This is a great benefit when you have numerous microservices.
The following makes use of with Java, Spring and Junit5.
Consumer End
https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-junit5
POM
<dependency>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-consumer-junit5_2.12</artifactId>
<version>3.5.24</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<pact.rootDir>target/pacts</pact.rootDir>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Test Example
import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.Pact;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.model.RequestResponsePact;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "test_provider", port = "1234")
public class ATest {
@Pact(provider="test_provider", consumer="test_consumer")
public RequestResponsePact createPact(PactDslWithProvider builder) {
return builder
.given("test state")
.uponReceiving("ExampleJavaConsumerPactTest test interaction")
.path("/")
.method("GET")
.willRespondWith()
.status(200)
.body("{\"responsetest\": true}")
.toPact();
}
@Test
void test(MockServer mockServer) throws Exception {
HttpResponse httpResponse = Request.Get(mockServer.getUrl() + "/").execute().returnResponse();
assertThat(httpResponse.getStatusLine().getStatusCode(), is(equalTo(200)));
}
}
Pacts File
{
"provider": {
"name": "test_provider"
},
"consumer": {
"name": "test_consumer"
},
"interactions": [
{
"description": "ExampleJavaConsumerPactTest test interaction",
"request": {
"method": "GET",
"path": "/"
},
"response": {
"status": 200,
"body": {
"responsetest": true
}
},
"providerStates": [
{
"name": "test state"
}
]
}
],
"metadata": {
"pactSpecification": {
"version": "3.0.0"
},
"pact-jvm": {
"version": "3.5.24"
}
}
}
Run the test
mvn clean test
This should create the pact file above in directory target/pacts
Producer End
POM
<dependencies>
<dependency>
<groupId>au.com.dius</groupId>
<artifactId>pact-jvm-provider-junit5_2.12</artifactId>
<version>3.5.24</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>
<configuration>
<systemPropertyVariables>
<pact.rootDir>target/pacts</pact.rootDir>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
Copy the pact file from the consumer on to the producer projects module target directory. The file can also be sent to a pact broker which the consumer can read from.
Producer test code:
@Provider("test-provider")
@PactFolder("target/pacts")
public class ProducerTest {
private String providerUrl = "http://localhost:8080/service/";
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context)
{
context.verifyInteraction();
}
@BeforeEach
void before(PactVerificationContext context) throws Exception {
context.setTarget(HttpTestTarget.fromUrl(new URL(
providerUrl)));
}
@State({"test state"})
public void toState() {
}
then run
mvn clean test
(you will need to first run the provider service)
Output should be verified like this:
Verifying a pact between consumer and test_provider
Given test state
GET REQUEST
returns a response which
has status code 200 (OK)
includes headers
"Content-Type" with value "application/json" (OK)
has a matching body (OK)
Using SpringBootTest (running a test spring runner) so you do not need to run the provider service separately:
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
properties = "server.port=8080")
@Provider("test-provider")
@PactFolder("src/test/resources/pacts")
public class SpringProducerTest {
@MockBean
private Service service;
@BeforeEach
void setupTestTarget(PactVerificationContext context) {
context.setTarget(new HttpTestTarget("localhost", 8080, "/context"));
}
@TestTemplate
@ExtendWith(PactVerificationInvocationContextProvider.class)
void pactVerificationTestTemplate(PactVerificationContext context) {
context.verifyInteraction();
}
@State({"test State"})
public void toState() {
when(service.get(anyString()).thenReturn("dummy");
}
}
Pack File Management
You can use a pact broker to house and serve up pact files. In which case the the pack file anotation is replaced by a pack broker annotion.
Alternatively if you have a repository then part of the pipeline for the consumer would be to publish to the repo and for the producer to retrieve the file from the repo before running the test.
Leave a comment
Your email address will not be published. Required fields are marked *