This library tries to reduce the overhead of writing the expectations of JSON structures in PACT consumer test. Normally you have to specify each field with its type and a sample value separately on PactDslJsonBody
. If the backend uses Java Beans as a representation for JSON structures, this library performs the necessary calls to the PACT consumer API. This avoids boilerplate code. Changes to your Java Bean model will be automatically reflected by the PACT consumer tests.
The following example shows what is necessary for a PACT consumer test. Assume you want to declare an object holding pricing information, like currency, amount etc. The following PACT consumer test would need the following code:
PactDslJsonBody jsonBody = new PactDslJsonBody().object("currency")
.numberType("id", expectedPricingResult.getCurrency()
.getId())
.stringType("name", expectedPricingResult.getCurrency()
.getName())
.stringType("isoCode", expectedPricingResult.getCurrency()
.getIsoCode())
.stringType("symbol", expectedPricingResult.getCurrency()
.getSymbol())
.closeObject()
.object("total")
// and more fields...
This mapping between the Java Bean and the JSON structure can be generated by this library. All you need to do is to declare what data types are to be mapped. For primitive types a default mapping comes out-of-the-box. For special requirements and custom data types you can specify individual mapping functions on a type or field basis.
The mapping code for the above structure reduces to
ConsumerExpects.type(PricingResultResource.class)
.useTypeMapping(ZonedDateTime.class, (body, fieldName, fieldValue) -> {
return body.stringType(fieldName, DEFAULT_FORMATTER.format(fieldValue));
})
.build(jsonBody, expectedPricingResult);
The above code shows that all primitive types can be automatically translated to respective calls on PactDslJsonBody
to declare the JSON structure. The full example uses a field with type ZonedDateTime
, so a custom converter for this type is added in the example.
You can find the full example here
This library converts Java Bean properties in respective calls on the PactDslJsonBody
API. Here are the data type mappings that are active by default:
Data type | Pact DSL Mapping |
---|---|
String | pactDslJsonBody.stringType(fieldName, fieldValue); |
byte/Byte | pactDslJsonBody.numberType(fieldName, fieldValue); |
short/Short | pactDslJsonBody.numberType(fieldName, fieldValue); |
int/Integer | pactDslJsonBody.integerType(fieldName, fieldValue); |
long/Long | pactDslJsonBody.integerType(fieldName, fieldValue); |
float/Float | pactDslJsonBody.numberType(fieldName, fieldValue); |
double/Double | pactDslJsonBody.decimalType(fieldName, (Double) fieldValue); |
boolean/Boolean | pactDslJsonBody.booleanType(fieldName, fieldValue); |
BigDecimal | pactDslJsonBody.decimalType(fieldName, (BigDecimal) fieldValue); |
If you want to map objects that does not comply to the Java Bean convention you can add a custom mapping in the following way:
ConsumerExpects.type(SomeJavaBeanType.class)
.useTypeMapping(NonJavaBeanType.class, (body, fieldName, fieldValue) -> {
return body.stringType(fieldName, asString(fieldValue));
})
The example shows how to add a mapping for the non-Java Bean type NonJavaBeanType
. You can specify a function that takes the PactDslJsonBody
, the field name and an example value for the field and invokes the respective methods on the PactDslJsonBody
instance. The function must return the resulting PactDslJsonBody
instance.
Global data type mappings apply to all fields of this type. You can override global mappings with field mappings.
You can reuse Java Bean mappings you declared using this library. If Java Bean references another Java Bean that was already mapped using this library, you can reuse the mapping like this:
ConsumerBuilder<Address> addressDefinition =
ConsumerExpects.type(Address.class)
// ... other mapping definitions...
;
ConsumerExpects.type(Person.class)
.referencing(addressDefinition)
// ... other mapping definitions...
If type Person
references Address
and the Address
structure was already defined, you can reuse the Address
as a reference from Person
. When building the JSON structure, the Address
will be mapped as defined in the registered ConsumerBuilder
.
Field mappings are used to override global mappings on a per-field basis or to introduce special cases. You can basically
- add type mappings for a specific field using a mapping function like described here Custom global data type mappings
- just define a custom JSON field name while using default mappings
- reuse another definition for a specific field
- define a custom JSON field name and reuse another definition for a specific field
Please refer to the JavaDoc or see here
To create a PACT consumer test for an endpoint that returns a collection as the top level element, use the special API entry point:
PactDslJsonArray pactDslJsonArray = ConsumerExpects.collectionOf(<LIST_ITEM_ELEMENT_TYPE>.class)
.useArraySupplier(supplier) // Use a custom array structure supplier.
.build(<LIST_ITEM_SAMPLE_HERE>);
For a complete example please refer to this example.
The PACT Dsl provides a way to define root values. Root values are values that can be represented by a simple string. When defining arrays using the PACT Dsl, root values must be declared using au.com.dius.pact.consumer.dsl.PactDslJsonRootValue
instead of using the methods that are used for fields within complex objects.
To support root values, this library provides a second interface com.remondis.cdc.consumer.pactbuilder.PactDslRootValueModifier<T>
. This interface provides the known methods of PactDslModifier
but also contains a mandatory method to return the specific PactDslJsonRootValue
.
If you define data types, that should be rendered as simple strings, always use PactDslRootValueModifier
when declaring a custom modifier.
Here is an example of a custom modifier definition for an object that should be represented as a simple string:
public class IntegerMapping implements PactDslRootValueModifier<Integer> {
// Define the PACT Dsl Json Body - relevant when used as a field within a complex object.
@Override
public PactDslJsonBody apply(PactDslJsonBody pactDslJsonBody, String fieldName, Integer fieldValue) {
return pactDslJsonBody.integerType(fieldName, fieldValue);
}
// Define the PACT Dsl Json Root Value - relevant when used as a simple value within an array.
@Override
public PactDslJsonRootValue asRootValue(Integer fieldValue) {
return PactDslJsonRootValue.integerType(fieldValue);
}
}
Pact consumer bodies can be build using the types PageBean
provided by this library. The original data types Page
, PageImpl
and Sort
cannot be used due to missing default constructors.
The following example shows how to build a pact consumer body for a Page
using the JavaBean-versions:
@Test
public void shuldGeneratePactFromPage() {
PageBean<Dto> pageBean = new PageBean<>(asList(new Dto("forename1", "name1"), new Dto("forename2", "name2")));
ConsumerExpects.type(PageBean.class)
.useTypeMapping(Sort.class, SpringSortModifier.sortModifier())
.field(PageBean::getContent)
.as(ConsumerExpects.type(Dto.class))
.build(new PactDslJsonBody(), pageBean);
}
The class com.remondis.cdc.consumer.pactbuilder.external.springsupport.SpringSortModifier
provides a custom JSON body definition for the class org.springframework.data.domain.Sort
. If you use this custom definition a sort structure will be defined in the resulting pact, but it uses sample data. So the sort values of your PageBean
object will not be reflected in the resulting pact.
Please refer to the project's contribution guide