Consumer Driven Contracts
A Deep Dive
Ramya Authappan
Lead SDET, Freshworks
Few Things About Myself
www.facebook.com/at.ramya
@atramya
https://www.linkedin.com/in/ramy
aat/
ramya.authappan@freshworks.c
om
http://tiny.cc/ramya_talks
Lead SDET @ Freshworks
Passionate about
building/designing Test
Frameworks/Tools
Believes in building “Simple, yet
Powerful” tools
Loves to play with Sharon
About Freshworks
• SAAS Startup
• Believes in building software that’s ready to go, easy to setup and use, and
requires minimal customization.
• Global Presence:
• Investors:
• Products:
CDC - Consumer Driven Contract!
• Microservices @ Amazon
• Talks to each other via HTTP Requests
• How do I make sure that a simple change does not break
anything in the application? An integration nightmare!
• In such a system, how do I independently AND
confidently make changes?
• CDC to the RESCUE!
CDC - Consumer Driven Contract!
• Consumer-Driven Contracts is a pattern defined by Martin Fowler.
• In Consumer-Driven Contracts, each consumer captures their expectations of the provider in a separate contract
(pact).
• All of these contracts are shared with the provider so they gain insight into the obligations they must fulfil for each
individual client.
• Implementations:
• Pact - https://docs.pact.io/ - This is what we will use in this session.
• Pacto - https://github.com/thoughtworks/pacto
• Janus - https://github.com/gga/janus
• Spring Cloud Contract - https://cloud.spring.io/spring-cloud-contract/ - Found this implementation few days
back. - Java Implementation
CDC - Consumer Driven Contract!
• What is Pact? - An opensource library implementing CDC
• Available as ruby gems (gem: ‘pact’) and as npm packages
(‘pact’)
• Supports: Ruby, Java, .NET
• Beta: JS,Python, Swift, Go
• Its called “Asynchronous” Integration Tests - No extra setup
needed.
• As simple as unit tests but as powerful as E2E tests.
• Pact is run on both sides - On “Consumer” side and on
“Provider” side.
CDC - Consumer Side
Sends
response
for
assertions
Tests
Set
Expectations
Pact Mock Provider
Tests
invoke
code
Source Code
Sends
HTTP
Requests
Sends
expected
response
CDC - Pact File
DevPortal Activities
Request
Response
Consumer Provider
CDC - 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.
CDC - Provider Side
Activities
Replay each HTTP Request
Get real HTTP Response
Provider
Pact File Compares real and
mock responses
CDC - Provider States
• Get activities of user with id=777
DevPortal Activities
Request
Response
Consumer Provider
Request
Response
?
CDC - Provider Side
state: cust_id=777 @ DB
• Get activities of user with id=777
DevPortal Activities
Request
Response
Consumer
Provider
Request
Response
state: cust_id=777 @ DB
state: cust_id=777 @ DB
CDC - Sharing Pact Files
Filesystem Cloud Pact BrokerCI Build
Artefact
CDC - Advantages!
• Eliminates Wrong Assumptions
• Enables communication
• Very less setup time!
• No extra infrastructure
• Need not start the dependent services to test!
• Fast in Execution
• Fails Fast - Identifies integration issues even before you actually
integrate!
• No flakiness!
• Easy to debug
Demo
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
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
Remember:
Consumer Assumed:
freshapps_activities.given(“
all activities without role
param")
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
Provider Side : Pact Verify Failure
Provider Side : Pact Verify Success
•My Upcoming Talks:
• WomenWhoCode August Meetup at Freshworks, Aug 19th - on “Docker and
CI”
Questions?
Thank you!

Consumer Driven Contracts - A Deep Dive

  • 1.
    Consumer Driven Contracts ADeep Dive Ramya Authappan Lead SDET, Freshworks
  • 2.
    Few Things AboutMyself www.facebook.com/at.ramya @atramya https://www.linkedin.com/in/ramy aat/ ramya.authappan@freshworks.c om http://tiny.cc/ramya_talks Lead SDET @ Freshworks Passionate about building/designing Test Frameworks/Tools Believes in building “Simple, yet Powerful” tools Loves to play with Sharon
  • 3.
    About Freshworks • SAASStartup • Believes in building software that’s ready to go, easy to setup and use, and requires minimal customization. • Global Presence: • Investors: • Products:
  • 4.
    CDC - ConsumerDriven Contract! • Microservices @ Amazon • Talks to each other via HTTP Requests • How do I make sure that a simple change does not break anything in the application? An integration nightmare! • In such a system, how do I independently AND confidently make changes? • CDC to the RESCUE!
  • 5.
    CDC - ConsumerDriven Contract! • Consumer-Driven Contracts is a pattern defined by Martin Fowler. • In Consumer-Driven Contracts, each consumer captures their expectations of the provider in a separate contract (pact). • All of these contracts are shared with the provider so they gain insight into the obligations they must fulfil for each individual client. • Implementations: • Pact - https://docs.pact.io/ - This is what we will use in this session. • Pacto - https://github.com/thoughtworks/pacto • Janus - https://github.com/gga/janus • Spring Cloud Contract - https://cloud.spring.io/spring-cloud-contract/ - Found this implementation few days back. - Java Implementation
  • 6.
    CDC - ConsumerDriven Contract! • What is Pact? - An opensource library implementing CDC • Available as ruby gems (gem: ‘pact’) and as npm packages (‘pact’) • Supports: Ruby, Java, .NET • Beta: JS,Python, Swift, Go • Its called “Asynchronous” Integration Tests - No extra setup needed. • As simple as unit tests but as powerful as E2E tests. • Pact is run on both sides - On “Consumer” side and on “Provider” side.
  • 7.
    CDC - ConsumerSide Sends response for assertions Tests Set Expectations Pact Mock Provider Tests invoke code Source Code Sends HTTP Requests Sends expected response
  • 8.
    CDC - PactFile DevPortal Activities Request Response Consumer Provider
  • 9.
    CDC - PactFile! • 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.
  • 10.
    CDC - ProviderSide Activities Replay each HTTP Request Get real HTTP Response Provider Pact File Compares real and mock responses
  • 11.
    CDC - ProviderStates • Get activities of user with id=777 DevPortal Activities Request Response Consumer Provider Request Response ?
  • 12.
    CDC - ProviderSide state: cust_id=777 @ DB • Get activities of user with id=777 DevPortal Activities Request Response Consumer Provider Request Response state: cust_id=777 @ DB state: cust_id=777 @ DB
  • 13.
    CDC - SharingPact Files Filesystem Cloud Pact BrokerCI Build Artefact
  • 14.
    CDC - Advantages! •Eliminates Wrong Assumptions • Enables communication • Very less setup time! • No extra infrastructure • Need not start the dependent services to test! • Fast in Execution • Fails Fast - Identifies integration issues even before you actually integrate! • No flakiness! • Easy to debug
  • 15.
  • 16.
    Consumer Side :Setup Mock Server Pact.service_consumer 'DevPortal' do end
  • 17.
    Consumer Side :Setup Mock Server Pact.service_consumer 'DevPortal' do has_pact_with "Freshapps Activities" do end end
  • 18.
    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
  • 19.
    Consumer Side :Setup Expectations freshapps_activities.given(“all activities without role param")
  • 20.
    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'} )
  • 21.
    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" } )
  • 22.
    Consumer Side :Make Request & Assert resp = FreshappsActivitiesClient.get_activities(path, query, header)
  • 23.
    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)
  • 24.
  • 25.
    Provider Side :Make Request & Assert Pact.service_provider 'Freshapps Activities' do end
  • 26.
    Provider Side :Make Request & Assert Pact.service_provider 'Freshapps Activities' do honours_pact_with 'DevPortal' do end end
  • 27.
    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
  • 28.
    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")
  • 29.
    Provider Side :Setup Test Data Remember: Consumer Assumed: freshapps_activities.given(“ all activities without role param") 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
  • 30.
    Provider Side :Pact Verify Failure
  • 31.
    Provider Side :Pact Verify Success
  • 32.
    •My Upcoming Talks: •WomenWhoCode August Meetup at Freshworks, Aug 19th - on “Docker and CI”
  • 33.
  • 34.