Upload
ramya-authappan
View
96
Download
0
Embed Size (px)
Citation preview
Pact Tests (Consumer Driven Contracts)
Based on a true (truly imaginary?) story!
Alice
• our main character in the story
• Senior Dev in FreshMock.com
• Owns a “Subscription Service”
• Friday the 13th!
What happened on Friday the 13th?
● “CustomerInfo Service”’s change
● Updates a property in the response object
● Changes “customer” (which returns a list of customers) to
“customers” (Grammatically correct though! :( )
● BUT this one letter change BROKE the “Subscription Service”
Here comes the bigger question…
Why didn't our tests catch this??
So how do you test?
● Mock the dependencies
Subscription Service Mock CustomerInfo Service
Request
Response
Unit Tests
● Similarly the “CustomerInfo Service”, has a mock client for
“Subscription Service”
● It DID fail!
● But then “I updated my test because I know this is an expected
failure! ”
Unit Tests
● Why do we still do it?
• Fast
• Easy to setup
• No Flakiness!
• Ensures functionality of a specific microservice
Integration Tests
Subscription Service CustomerInfo Service
Request
Response
Integration Tests
● Slower
● Set up time (“Stack Setup!”)
● More flakiness
● More infrastructure!
● End up doing an E2E tests
Other types of Tests•End-to-End Tests
•Manual Tests
•Again,
• Stack Setup!
• Slower (E2E tests)
• More flakiness
• Not failing fast!
Solution?
Is there a better way to identify
these kind of issues?
Pact Tests
• Also called “Contract Tests”
• Consumer Driven Contract Tests (CDC Tests)
• Tool Used: Pact • A Contract is a collection of agreements between a client (Consumer)
and an API (Provider) that describes the interactions that can take
place between them.
Pact Tests? What are these?
Subscription Service CustomerInfo Service
Request
Response
Consumer Provider
Sends response
for assertions
Pact Tests: Consumer Side
Tests
Set Expectations
Pact Mock Provider
Tests invoke code
Source Code
Sends HTTP
Requests
Sends expected response
Is it just another Mock HTTP Server?
Subscription Service CustomerInfo Service
Request
Response
Consumer Provider
The Pact File
• Every HTTP Request and Response is
captured
• Standard way of representing
interactions - JSON file
• This is shared with the Provider.
• Explains everything that a consumer
expects from the Provider - the
endpoints, query params, header and
the response objects.
Pact Tests:Provider Side
CustomerInfo Service
Replay each HTTP Request
Get real HTTP Response
ProviderPact File Compares real and
mock responses
Provider States
• Get billing address of customer
with id=777
Subscription Service CustomerInfo Service
Request
Response
Consumer Provider
Request
Response
?
state: cust_id=777 @ DB
Provider States
• Get billing address of customer
with id=777
Subscription Service CustomerInfo Service
Request
Response
Consumer Provider
Request
Response
state: cust_id=777 @ DB
state: cust_id=777 @ DB
Sharing Pact Files
Filesystem Cloud Pact BrokerCI Build Artefact
Sharing Pact Files - Pact Broker
• Pact Broker - a repo for storing pacts
• Auto-generated documentation
• The ability to tag a Pact (i.e. “prod") -
versioning pacts
• Network Graph
Sharing Pact Files - Pact Broker
Automating Pact Tests
• When a new Pact is published, how does the Provider get notified?
Manual Trigger Pact Broker
Webhook
Swagger Validator
Advantages
•Eliminates Wrong Assumptions
•Enables communication
•Very less setup time!
•No extra infrastructure
•Fast in Execution
•Fails Fast
•No flakiness!
•Easy to debug
DEMO
Freshapps DevPortal Freshapps Activities
Request
Response
Consumer Provider
Consumer Side : Setup Mock Server
Pact.service_consumer 'DevPortal' do
end
Consumer Side : Setup Mock Server
Pact.service_consumer 'DevPortal' do has_pact_with "Freshapps Activities" do
end end
Consumer Side : Setup Mock Server
Pact.service_consumer 'DevPortal' do has_pact_with "Freshapps Activities" do mock_service :freshapps_activities do port 3005 end end end
Consumer Side : Setup Expectations
freshapps_activities.given(“all activities without role param")
Consumer Side : Setup Expectations
freshapps_activities.given(“all activities without role param") .upon_receiving("a request for all activities") .with( method: :get, path: '/all/activities.json', query: { page: '1', account_id: '1' }, headers: {'Content-Type' => 'application/json', 'Accept' => 'application/json'} )
Consumer Side : Setup Expectations
freshapps_activities.given(“all activities without role param") .upon_receiving("a request for all activities") .with( method: :get, path: '/all/activities.json', query: { page: '1', account_id: '1' }, headers: {'Content-Type' => 'application/json', 'Accept' => 'application/json'} ) .will_respond_with( status: 422, headers: { 'Content-Type' => 'application/json; charset=utf-8' }, body: { "error_msg" => "Required parameter missing" } )
Consumer Side : Make Request & Assert
resp = FreshappsActivitiesClient.get_activities(path, query, header)
Consumer Side : Make Request & Assert
resp = FreshappsActivitiesClient.get_activities(path, query, header) expected_response = { "error_msg" => "Required parameter missing" } expect(resp.parsed_response.inspect).to eq(expected_response.inspect)
Generated Pact File { "consumer": { "name": "DevPortal" }, "provider": { "name": "Freshapps Activities" }, "interactions": [ { "description": "a request for all activities", "provider_state": "all activities without role param", "request": { "method": "get", "path": "/all/activities.json", "query": "page=1&account_id=1", "headers": { "Content-Type": "application/json", "Accept": "application/json" } }, "response": { "status": 422, "headers": { "Content-Type": "application/json; charset=utf-8" }, "body": { "error_msg": "Required parameter missing" } } } ], "metadata": { "pactSpecificationVersion": "1.0.0" } }
Provider Side : Make Request & Assert
Pact.service_provider 'Freshapps Activities' do
end
Provider Side : Make Request & Assert
Pact.service_provider 'Freshapps Activities' do honours_pact_with 'DevPortal' do end end
Provider Side : Make Request & Assert
Pact.service_provider 'Freshapps Activities' do honours_pact_with 'DevPortal' do pact_uri "../freshapps_devportal/spec/pacts/devportal-freshapps_activities.json" end end end
Provider Side : Setup Test Data
Pact.provider_states_for "DevPortal" do provider_state "all activities without role param" do
end end
Remember: Consumer Assumed:
freshapps_activities.given(“all activities without role
param")
Provider Side : Setup Test Data
Pact.provider_states_for "DevPortal" do provider_state "all activities without role param" do set_up do factory :activity do account_id 1 user_id 1 extension_id 2 version_id 2 activity_type 'Extension' visibility 1 activity { 'extension_id' => '2', 'name' => 'Sample Extension’} end end end end
Remember: Consumer Assumed:
freshapps_activities.given(“all activities without role
param")
Provider Side : Pact Verify Failure
Provider Side : Pact Verify Success
pact.io
• Working examples and documentations available at pact.io
• Supports: Ruby, Java, .NET
• Beta: JS,Python, Swift, Go
• https://groups.google.com/forum/#!forum/pact-support
• https://gitter.im/realestate-com-au/pact
• Other Similar Tools:
• Pacto (by Thoughtworks - but not maintained though.)
References
● To know about Consumer Driven Contract: https://
martinfowler.com/articles/consumerDrivenContracts.html
● https://www.thoughtworks.com/radar/techniques/
consumer-driven-contract-testing
And… What happened to Alice?
Oh yea! She implemented “Pact Tests”
and
lived happily ever-after!!
Questions
Thank You!