collaborating with contracts
TRANSCRIPT
REAL LIFE: CONTRACTSProvider
Ice cream supplierConsumerSupermarket Contract
must order 1 week in advancemust pay at time of order
must deliver within 3 business daysproduct must not expire within 2 months
SERVICE
• a reusable software component encapsulating a business function
• can be exposed over HTTP, queue, ...
PERSON SERVICE
ProviderConsumers
A
B
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
NEW CONSUMER, NEW TROUBLE
Provider
Consumers
A
B
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
C
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
UPDATE PROVIDER DIRECTLY
Provider
Consumers
A
B
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
C
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
CONSUMERS A AND B BREAKS
Provider
Consumers
A
B
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
C
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
CONSUMER-DRIVEN CONTRACTS
ProviderConsumers
A
B
C
Contract A
Contract B
Contract C
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
WHY IS THIS USEFUL?
• services are only useful when they are used
• consumer-driven contracts makes consumer expectations explicit
AN EXAMPLE
Provider
Consumers
A
B
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26,friends: ...
}
{id: 1,name: “Jane”,age: 26
}
{id: 1,name: “Jane”,age: 26
}
C
{id: 1,first_name: “Jane”,last_name: “Good”,age: 26
}
$$$$
PLAYS WELL WITH TDD
• write consumer-driven contract test for consumer C
• red-green-refactor : consumer side
PLAYS WELL WITH TDD
• write consumer-driven contract test for consumer C
• red-green-refactor : consumer side
• red-green-refactor : provider side
PLAYS WELL WITH TDD
• write consumer-driven contract test for consumer C
• red-green-refactor : consumer side
• red-green-refactor : provider side
• make sure consumer-driven contract tests for consumers A and B still passes too
ProviderConsumer ServiceBoundary
Provider stub
testing around service boundary
{id: 1,name: “Jane”,age: 26
}
ProviderConsumer ServiceBoundary
Provider stub
unit test on consumer:uses a provider stub
verifies the consumer processes the stubbed response properly
{id: 1,name: “Jane”,age: 26
}
ProviderConsumer ServiceBoundary
unit test on provider :use fake consumer
tests that given the right request, it returns the right response
Fake consumer
ProviderConsumer ServiceBoundary
Provider stub
there could be a mismatch
{id: 1,name: “Jane”,age: 26,address: ...
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
ProviderConsumer ServiceBoundary
Provider stub
unit tests pass, but app will blow up
{id: 1,name: “Jane”,age: 26,address: ...
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
ProviderConsumer ServiceBoundary
Provider stub
both sides are verified against the same contract
{id: 1,name: “Jane”,age: 26,address: ...
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Contract
ProviderConsumer ServiceBoundary
Provider stub
write expectations from the consumer side
{id: 1,name: “Jane”,age: 26,address: ...
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Contract
ProviderConsumer ServiceBoundary
Provider stub
generate the contract
{id: 1,name: “Jane”,age: 26,address: ...
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Contract
ProviderConsumer ServiceBoundary
Provider stub
verify that the provider adheres to the same contract
{id: 1,name: “Jane”,age: 26,address: ...
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Contract
fail:address is missing!
ProviderConsumer ServiceBoundary
Provider stub
consumer sends the right request
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Contract
ProviderConsumer ServiceBoundary
Provider stub
provider returns the right response
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Contract
TOOL: PACTgithub.com/realestate-com-au/pact
ProviderConsumer ServiceBoundary
Provider stub
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
ProviderConsumer ServiceBoundary
Provider stub
write the tests for the consumer
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
person_service. given("a person with too many friends"). upon_receiving("request for person details").
with(method: :get, path: "/person/1", query: '').
person_service. given("a person with too many friends"). upon_receiving("request for person details").
with(method: :get, path: "/person/1", query: '').
person_service. given("a person with too many friends"). upon_receiving("request for person details").
will_respond_with(status: 200, headers: {'Content-Type' => 'application/json; charset=utf-8'}, body: {
id: 1, name: “Jane”, age: 26, address: ... })
with(method: :get, path: "/person/1", query: '').
person_service. given("a person with too many friends"). upon_receiving("request for person details").
will_respond_with(status: 200, headers: {'Content-Type' => 'application/json; charset=utf-8'}, body: {
id: 1, name: “Jane”, age: 26, address: ... })
expect(subject.person_details(“1”)).to eq(expected_person)
ProviderConsumer ServiceBoundary
Provider stub
runwatch it failmake it pass
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
ProviderConsumer ServiceBoundary
Provider stub
the consumer tests generates a “pact”
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Pact
ProviderConsumer ServiceBoundary
Provider stub
configure to verify selected pact(s)
{id: 1,name: “Jane”,age: 26
}
Fake consumer
{id: 1,name: “Jane”,age: 26
}
Pact
person_service. given("a person with too many friends"). upon_receiving("request for person details").
provider states
given("a person with too many friends").
person_service. given("a person with too many friends"). upon_receiving("request for person details").
provider states
provider_state "a person with too many friends" do set_up do
10_000.times doperson = Person.firstperson.friends << Person.new(...)
end end end
will_respond_with(status: 200, headers: {'Content-Type' => 'application/json; charset=utf-8'}, body: {
id: 1, name: Pact::SomethingLike.new(“Jane”), age: 26, address: ... })
loose matching
with(method: :get, path: "/person/1", query: '').
person_service. given("a person with too many friends"). upon_receiving("request for person details").
Pact::SomethingLike.new(“Jane”)
USE CASES
•making consumer expectations explicit
•making sure both consumer and provider do their job
USE CASES
•making consumer expectations explicit
•making sure both consumer and provider do their job
• enabling parallel development