Pact with Java (by example)
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.