End-to-end integration plays a strong part in testability, unfortunately when an application grows, these kind of tests become a burden: brittleness, slower feedback and overall poor return on investment to improve quality.
Contract testing brings an alternative approach for validating integration points in fast-changing distributed systems. Because contracts don’t need integration environments, they can give very fast feedback to prevent API and messaging breaking changes from being introduced early-on.
Contracts are also a catalyst for inter-team communications. They help interactions between services become a central attribute in designing solutions, as opposed to an emergency concern when they break at a late integration stage.
This workshop covers the core concepts of contracts testing and contracts can play a part in reducing the struggles of integration tests. The attendees will be working on practical examples of defining contracts between teams and services, as well as implement them using the Pact tool-chain.
9. What’s in a contract?
Request Headers
HTTP MethodAPI Endpoint
Query Parameters Request Body
Response HeadersStatus Code Response Body
e.g. /api/users e.g. POST
e.g. ?fields=name e.g. Authorization
e.g. Content-Typee.g. 200
10. Our running example for the workshop
Game
Service
Leaderboard
Service
APIAPI
Play a game
Return result &
current win-rate
Record game result
Return updated
win-rate
Contract
11. POST /play
{
"username": "pierre",
"game": "headsOrTails",
"choice": "heads"
}
Game
Service
API
200 OK
Content-Type: application/json; charset=UTF-8
{
"won": true,
"message": "You won!",
"totalPlays": 120,
"totalWins": 71,
"winRate": 59
}
13. Game
Service
Leaderboard
Service
API
Record game result:
Game played, User
playing, Won/Lost
Return win-rate:
Games played,
Games won,
Win-Loss Ratio
“Consumer” Teams
- Document what API you need to send
game results
“Provider” Teams
- Document what API you will
implement to receive game results
14. Game
Service
Leaderboard
Service
API
Record game result:
Game played, User
playing, Won/Lost
Return win-rate:
Games played,
Games won,
Win-Loss Ratio
“Consumer” & “Provider” Teams
- Meet to compare your API designs
- Discuss differences and come to agreement on the API interaction
- Have you thought of “unhappy” paths? (API errors)
17. Consumer
Provider
Provider State
Request
Expected
Response
Login Service
User Service
Given that user 'pierre' exists
Method GET
Path /users/pierre
Headers
Accept: application/json
Status 200
Headers
Content-Type: application/json
Body
{
"user": "pierre",
"name": "Pierre Vincent",
"role": "publisher"
}
Interaction
18. User
Service
A
P
I
1. Set Provider State
2. Send Request
3. Verify Response
Interaction Verification Test
INSERT INTO users [...]
GET /users/pierre
Accept: application/json
{
"user": "pierre",
...
}
{
"user": "pierre",
...
}
?
20. RecordScore API (or write your own)
Game
Service
Leaderboard
Service
POST /recordScore
{
“username”: “anna”,
“game”: “headOrTails”,
“won”: true
}
{
“gamesPlayed”: 123,
“gamesWon”: 55
}
Request
Response
21. API
Implement API client in Consumer
- In LeaderboardClient.java:
- API endpoint (recordScoreUrl)
- Request Body (RecordScoreRequestBody)
- Response Body (RecordScoreResponseBody)
./gradlew run
Run the service (in game-service dir)
curl -X POST -H "Content-Type: application/json" --data '{"username":
"pierre", "game":"headsortails", "choice": "tails"}'
http://localhost:8080/play
Try your API
Game
Service
22. API
Creating the Pact
- In LeaderboardClientPactTest.java:
- Complete the PACT definition (RequestResponsePact)
- Update expected response (expectedResponse)
- Update expected request (client.recordScore)
./gradlew test
Run the pact test (in game-service dir)
cat build/pacts/game-service-leaderboard-service.json
Find the generated PACT
Contract
23. Leaderboard
Service
API
Implement the Provider API
- In ScoreController.java:
- API endpoint (@PostMapping)
- Request Body (RecordScoreRequestBody)
- Response Body (RecordScoreResponseBody)
./gradlew run
Run the service (in leaderboard-service dir)
curl -X POST -H "Content-Type: application/json" --data
'YOUR_JSON_PAYLOAD' http://localhost:8081/your/api/endpoint
Try your API
24. Verifying the pact
- Copy the generated PACT file (game/build/pacts/*.json)
to the Provider test resources (leaderboard-service/src/test/resources/pacts)
- Complete the PACT definition (RequestResponsePact)
- Update expected response (expectedResponse)
- Update expected request (client.recordScore())
./gradlew test
Run the verification test (in leaderboard-service dir)
Contract
✓
29. Provider pipeline
Implement changes Get Pacts from Broker
Replay & Verify
Interactions
Deploy Service
Build
Deploy to EU
PROD-EU
Get Pacts from Broker
Replay & Verify
Interactions
Stop deployment of
incompatible Provider
Stop introduction of
breaking change
PROD-US
PROD-EU