TODO: Add code quality
TODO: Add code repository
This is a helper tool for Contract Testing with Pact. An annotation processor designed to generate DslPart objects for the body definitions based on annotations included in your model classes.
The focus is to simplify JVM contract testing implementation, minimizing the amount of boilerplate code needed.
Specially useful when defining body validations for interactions with complex models.
-
JDK +17
-
Having inside your project a verification library of your choice to have the
@Max
and@Min
annotations available, such as Jakarta, Spring Boot or similar.
Pact DSL Builder | Pact JVM |
---|---|
1.1.0 | +4.6.3 |
1.1.7 | +4.6.3 |
1.2.0 | +4.6.3 |
The only configuration needed for starting using the library is adding the dependency to your build automation tool:
Maven
<dependencies>
...
<dependency>
<groupId>com.sngular</groupId>
<artifactId>pact-annotation-processor</artifactId>
<version>1.2.0</version>
</dependency>
...
</dependencies>
Gradle
implementation('com.sngular:pact-annotation-processor:1.2.0')
To enable the code generation in a model class, you should annotate it as @PactDslBodyBuilder
.
That is the only requirement, all other annotations are optional and used for customising the generated code.
We have developed 3 annotations to give support to your needs,
@PactDslBodyBuilder
: To indicate which class you need to generate pact to.@Example
: To define constants values to set in your Pact Body.DslExclude
: To Exclude some property to be included in the builder.
and support 2 standard Java annotations for validation
@Min
: From Jakarta or Javax (or other validation tools) to indicate the minimum value to be cover for this property.@Max
: From Jakarta or Javax (or other validation tools) to indicate the maximum value to be cover for this property.
Annotation | Required | Level | Description |
---|---|---|---|
@PactDslBodyBuilder |
true | Class | Main annotation, to be included in classes that want to be processed. |
@Example |
false | Field | Used to provide an specific value for a field. If not present in a field, the value created will be random. |
@Min |
false | Field | Defines the maximum value for numeric fields, or number of elements if applied to collections. Will be ignored if an @Example is present. |
@Max |
false | Field | Defines the minimum value for numeric fields, or number of elements if applied to collections. Will be ignored if an @Example is present. |
@DslExclude |
false | Field | Ignore de generation of example values. |
@Example
values are always provided as String. If a specific format is required lets say for date and datetime properties, then a format field should be provided, otherwise it will fall back to default format. For date and datetime default format are:
yyyy-MM-dd['['ZZZ']']
: for dates
yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][.SSS]XXX['['VV']']
: for datetimes. Zone should be provided with this format.
package com.sngular.model;
import com.sngular.annotation.pact.Example;
import com.sngular.annotation.pact.PactDslBodyBuilder;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.time.ZonedDateTime;
@PactDslBodyBuilder
public class Address {
@Example("2023-12-03T10:15:30+01:00[Europe/Madrid]")
private ZonedDateTime deliveryTime;
@Example("2023-12-13")
private Date creationDate;
@Example("Jose")
private String name;
@Max(12)
@Min(1)
private int number;
@Example("4")
private long aLong;
private ZonedDateTime init;
private City city;
}
Once the code is compiled, builder will be generated and available under generated-sources
in your build directory.
You will need to add the required import, and make use of the related builder class.
import com.sngular.model.AddressBuilder;
AddressBuilder addressBuilder = new AddressBuilder();
DslPart bodyDslPart = addressBuilder.build();
@Pact(consumer = "consumer-poc", provider = "provider-poc")
public RequestResponsePact getStudents(PactDslWithProvider builder) {
AddressBuilder addressBuilder = new AddressBuilder();
DslPart bodyDslPart = studentBuilder.build();
return builder.given("Address exist")
.uponReceiving("get all address")
.path("/address/")
.method("GET")
.willRespondWith()
.status(200)
.headers(Map.of("Content-Type", "application/json"))
.body(bodyDslPart)
.toPact();
}
- Dates: Regarding Timestamp and Date, we should use keep in mind the default
formats will be used to parse those values:
- For Dates, we are using
"yyyy-MM-dd['['ZZZ']']"
as default format - For Timestamps, we are using
"yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][.SSS]XXX['['VV']']"
as default format for datetime (ZonedDateTime) If you need and specific format the@Example
support a format property to handle them.
- For Dates, we are using
=======
In certain situations, especially when using the @Example
annotation in all
of your model data objects, you may prefer to perform classic manual
instance creation for your validations in the @Test
methods:
@Test
@PactTestFor(pactMethod = "getAddressTest")
void getAddressTest(MockServer mockServer) {
RestTemplate restTemplate = new RestTemplateBuilder().rootUri(mockServer.getUrl()).build();
Address response = new BasicService(restTemplate).getAdress("...");
// Manual instance creation for validation
Address expectedAddress = new Address();
expectedAddress.setName("Jose");
expectedAddress.setNumber(12);
// ...
assertEquals(expectedAddress, response);
}
However, in many situations, especially when dealing with random values being generated,
you may prefer to delegate the instance creation to the library itself.
This is the purpose of the buildExpectedInstance()
method, which generates an
instance of the model object initialized with the values generated (or set
by you with @Example
) for the given object:
@Test
@PactTestFor(pactMethod = "getAddressTest")
void getAddressTest(MockServer mockServer) {
RestTemplate restTemplate = new RestTemplateBuilder().rootUri(mockServer.getUrl()).build();
Address response = new BasicService(restTemplate).getAdress("...");
// Using buildExpectedInstance() for instance creation
Address expectedAddress = new AddressBuilder().buildExpectedInstance();
assertEquals(expectedAddress, response);
}
TODO: Define roadmap
Contributions are what makes the open source community special. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this library better, please review our contributing guidelines.
Or you can simply open a feature request issue.
Distributed under Mozilla Public License Version 2.0. See LICENSE for more information