Pact with Java (by example)

Francislainy Campos
HMH Engineering
Published in
5 min readOct 6, 2020

Small DSL samples so you can give Stack Overflow a bit of space.

I’ve recently written an article talking a little bit about how to set up Pact tests with JUnit and REST Assured. However, it may not be what you need in case you are already doing Pact but just trying to figure out how to code for a specific api structure. For this reason, I’m pasting here some samples of API responses and their equivalent in DSL, the language Pact uses, so that you don’t need to try and find this info elsewhere and can leave Stack Overflow a bit alone.

1 —Integers and empty array

{
"totalMeetings":0,
"totalPages":0,
"pageSize":20,
"pageNumber":0,
"meetings":[]
}

Becomes

DslPart body = new PactDslJsonBody()
.integerType("totalMeetings", 3)
.integerType("totalPages", 1)
.integerType("pageSize", 20)
.integerType("pageNumber")
.array("meetings")
.closeArray();

Please note we are using .integerType rather than integerValue . This is because in our tests we care whether our field returns as an integer, however, which exact value is there wouldn’t bother us much. If we do care about this value though, then we would be using the aforementioned integerValue buddy. The same applies for stringType versus stringValue , booleanType versus booleanValue , etc.

2 — Integers and array that contains number, string and date fields

{
"totalMeetings":1,
"totalPages":1,
"pageSize":20,
"pageNumber":0,
"meetings":[
{
"id":508,
"title":"A title",
"startDateTime":"2020-10-17T15:00:00:00Z",
"duration":30
}
]
}

Becomes

DslPart body = new PactDslJsonBody()
.integerType("totalMeetings", 3)
.integerType("totalPages", 1)
.integerType("pageSize", 20)
.integerType("pageNumber")
.eachLike("meetings")
.numberType("id", 93)
.stringType("title", "Any sample title")
.date("startDateTime", "yyyy-MM-dd'T'HH:mm:ss'Z'", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse("2020-10-17T15:00:00:00Z"))
.integerType("duration", 0)
.closeArray();

Please notice here that we’re using .numberType for the id field to allow for it to come back as along datatype since this may be the case if your database holds too many entries and the id’s are growing in proportion to this. For the date type we are using .date passing a formatter as a parameter to parse that field using a date as a sample value.

We’re also using .eachLike for the array so that we can inspect for patterns in case our api returns multiple items rather than only one. If you are sure your api should return only one item and if it does it differently that would be an issue on your application, you can use .maxArrayLike("meetings", 1) where “1” would be whatever number is your maximum. On the other hand, if you want to make sure there is always a minimum number, you can go with their .minArrayLike("meetings", 1) counterpart instead.

It’s also important to notice that if your array is returning multiple items, each and every one of them need to have the same structure, otherwise your tests may fail. Using the above example, each item within the “meetings” array should contain an id , title , date , and duration and each being of the same data type as previously identified on the Pact contract. If for example, an item comes back without a date or with duration as null, the assertion will fail.

3 — Objects

{
"totalMeetings":1,
"totalPages":1,
"pageSize":20,
"pageNumber":0,
"meetings":[
{
"id":508,
"title":"A title",
"startDateTime":"2020-10-17T15:00:00+0000",
"duration":30,
"attributes":{
"meetingId":"d7a8977s-fcee-4eab-8977-1d800a8b818e",
"joinUrl":"https://meeting.url.com"
},
"provider":"My Provider",
"creator":{
"id":1,
"userRefId":"d7af5c68-fcee-4eab-8977-1d800a8b818e",
"attributes":null
},
"participants":[

]
}
]
}

Becomes

DslPart body = new PactDslJsonBody()
.integerType("totalMeetings", 3)
.integerType("totalPages", 1)
.integerType("pageSize", 20)
.integerType("pageNumber")
.eachLike("meetings")
.integerType("id", 93)
.stringType("title", "Any title")
.date("startDateTime", "yyyy-MM-dd'T'HH:mm:ss'Z'", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(nowAsISO))
.integerType("duration", 0)
.stringType("provider", "My Provider")
.object("attributes" )
.stringType("meetingId", "d7a8977s-fcee-4eab-8977-1d800a8b818e")
.stringType("joinUrl", "https://meeting.url.com")
.closeObject()
.object("creator")
.integerType("id", 1)
.stringType("userRefId", "d7af5c68-fcee-4eab-8977-1d800a8b818e")
.nullValue("attributes")
.closeObject()
.array("participants")
.closeArray();

Please note the usage for .nullValue for a case where we only want to check whether a certain field is present even though it doesn’t hold anything inside it.

Something worth mentioning as well and that may make you curious (or confuse) at first is that your contract may be generated not necessarily following the same order as you have coded it. This can be seen below where the “title” key is displayed more at the bottom of the list whereas it was actually originally added in the middle of the other keys.

"body": {
"pageNumber": 100,
"totalPages": 1,
"pageSize": 20,
"totalMeetings": 3,
"meetings": [
{
"duration": 0,
"creator": {
"userRefId": "d7af5c68-fcee-4eab-8977-1d800a8b818e",
"attributes": null,
"id": 1
},
"startDateTime": "2020-09-21T13:44:11Z",
"provider": "ZOOM",
"attributes": {
"meetingId": "d7a8977s-fcee-4eab-8977-1d800a8b818e",
"joinUrl": "https://meeting.url.com"
},
"id": 93,
"title": "My new title",
"participants": []
}
]
},

Similar issue happens when you may be creating your DSL and trying to have that key or any other pasting it after your objects. Your IDE may complain about that and only allow you to paste it before any arrays or object elements.

4 — Array holding objects

{
"totalMeetings":1,
"totalPages":1,
"pageSize":20,
"pageNumber":0,
"meetings":[
{
"id":508,
"title":"A title",
"startDateTime":"2020-10-17T15:00:00+0000",
"duration":30,
"attributes":{
"meetingId":"d7a8977s-fcee-4eab-8977-1d800a8b818e",
"joinUrl":"https://meeting.url.com"
},
"provider":"ZOOM",
"creator":{
"id":1,
"userRefId":"d7af5c68-fcee-4eab-8977-1d800a8b818e",
"attributes":null
},
"participants":[
{
"id":88,
"userRefId":"01f91a87-ce56-4d21-86bc-ec6419cf27e7",
"attributes":{
"name":"Student 1",
"email":"student@coolschool"
}
}
]
}
]
}

Becomes

DslPart body = new PactDslJsonBody()
.integerType("totalMeetings", 3)
.integerType("totalPages", 1)
.integerType("pageSize", 20)
.integerType("pageNumber")
.eachLike("meetings")
.integerType("id", 93)
.stringType("title", "Any title")
.date("startDateTime", "yyyy-MM-dd'T'HH:mm:ss'Z'", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(nowAsISO))
.integerType("duration", 0)
.stringType("provider", "My Provider")
.object("attributes")
.stringType("meetingId", "d7a8977s-fcee-4eab-8977-1d800a8b818e")
.stringType("joinUrl", "https://meeting.url.com")
.closeObject()
.object("creator")
.integerType("id", 1)
.stringType("userRefId", "d7af5c68-fcee-4eab-8977-1d800a8b818e")
.nullValue("attributes")
.closeObject()
.eachLike("participants")
.numberType("id", 88)
.stringType("userRefId", "d7af5c68-fcee-4eab-8977-1d800a8b818e")
.object("attributes")
.stringType("name", "User 1")
.stringType("name", "myemail@email.com")
.closeObject()
.closeArray();

For the “participants” array we are using .eachLike rather than .array as we have more than one element inside it. Besides this, as mentioned before, with .eachLike we can be asserting against multiple items coming back from the api.

5 — Array as starting point

All the examples we have seen so far use an object as the starting point. However, for scenarios like the below, where we are starting with an array, we can make use of PactDslJsonArray.arrayMaxLike(1) rather than PactDslJsonBody

[
{
"studentId": "76ee3517-4469-4f48-8686-a6c8cc8e0a90",
"skills": [
{
"id": "5756261f-5588-4342-860a-4e1b1939b58c",
"name": "",
"primary": true
},
{
"id": "aee5fa52-2638-4264-8804-4eafaf16a879",
"name": "",
"primary": true
},
{
"id": "544c07c1-75b4-4a9b-b6fe-50f5a0708457",
"name": "",
"primary": true
}
],
"metaData": {
"recommendationReason": "This student is progressing towards proficiency."
}
}
]

Becomes

DslPart body = PactDslJsonArray.arrayMaxLike(1)
.stringType("studentId", "1cd1bfa6-87a7-4bea-8025-fb7cd159f1fb")
.object("metaData")
.stringType("recommendationReason", "This student is progressing towards proficiency.")
.closeObject()
.eachLike("skills", 2)
.stringType("id", "544c07c1-75b4-4a9b-b6fe-50f5a0708457")
.stringType("name", "")
.booleanType("primary", true)
.closeArray();

We can also notice as something extra here the usage for booleanType for checking against a boolean variable.

And that’s it for today.

Thank you for reading this article and I hope you may have found it useful.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in HMH Engineering

HMH Engineering builds fantastic software to meet the challenges facing teachers and learners. We enable and support a wide range of next-generation learning experiences, designing and building apps and services used daily by millions of students and educators across the USA.

Written by Francislainy Campos

I like coding, cycling, K-Pop (girl groups), Pokémon and chocolate.

Responses (1)

What are your thoughts?

Thank you, your post helped me.