pact-foundation / pact-ruby

Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://pact.io
MIT License
2.17k stars 216 forks source link

How to setup for simultaneous provider and consumer? #176

Open embs opened 6 years ago

embs commented 6 years ago

Hello!

Thank you very much for Pact. It's awesome.

This is more of a conceptual question than a bug report, so I'm not providing much code but I would happily arrange it if necessary. Also, I guess this is a common use case but I wasn't able to find examples for it -- that's why I'm filing this issue. Hopefully, it can be addressed by only referring to some examples.

I'm using Pact for testing a services architecture like this:

service A --consumes--> service B --consumes--> service C

My intended approach is writing tests by hand only for service A and having Pact to verify proper behavior for services B and C based on generated pacts. I was able to generate the pact a-b.json from A's tests execution and now I want to use it for verifying B while generating b-c.json pact.

My attempt for this looks like this:

# ./spec/service_consumers/pact_helper.rb inside B

Pact.service_provider 'B' do
  honours_pact_with 'A' do
    pact_uri 'a-b.json'
  end
end

Pact.service_consumer 'B' do
  has_pact_with 'C' do
    mock_service :c_service do
      port 1234
    end
  end
end

# This is how I accomplished mocking B -> C requests
c_service = Pact.consumer_world.consumer_contract_builders.first
c_service.given('some state')
  .upon_receiving('some request')
  .with(method: :get, path: '/resource')
  .will_respond_with(status: 200, body: [])

# This is for writing `b-c.json` so I can verify C with Pact
RSpec.configure do |config|
  config.after(:suite) do
    Pact.consumer_world.consumer_contract_builders.each { |c| c.write_pact }
  end
end

This seems to satisfy my use case but I was expecting a cleaner way for mocking and writing the pact file. Specifically, I feel I'm missing what happens within RSpec specs when we assign pact: true: disposal of c_service instance on the fly and automatically written pact file.

I wonder if this is a common use case and how to appropriately tackle it. Hope you can guide me in the right direction.

Thanks again, Matheus

bethesque commented 6 years ago

This setup makes me feel a bit nervous because it's all so very highly coupled. But I'll work with you on it. A little spike codebase would really help work this out. Are you requiring "pact/consumer/rspec" anywhere? That's the thing that hooks up the after suite hooks.

If you do this, it should make your consumer contract builder available by name in the provider state set up blocks:

Pact.configure do | config |
    config.include Pact::Consumer::RSpec
end
babelian commented 5 years ago

I had a similar scenario, but opted to just use allow() to stub service C in the provider_state setup (of the A<>B pact verification). As long as Service B has complete specs, we still get coverage when it runs its own specs (with Pact mock servers for B->C).

I've just tried switching it out for the method above, but found that because Pact raises an error if the request does not happen its easier to use allow() which is indifferent about the order of calls or whether they happen at all. To use Pact within the provider one would either have to make very granular provider states or perhaps wait for access to v3 params to determine which things to stub (at all a bit messy).