Provider States

See Provider States for an introduction into this advanced topic.

The text in the provider state should make sense when you read it as follows (this is how the auto-generated documentation reads):

Given an alligator with the name Mary exists *
Upon receiving a request to retrieve an alligator by name ** from Some Consumer
With {“method” : “get”, “path” : “/alligators/Mary” }
Some Provider will respond with { “status” : 200, …}

* This is the provider state
** This is the request description

Consumer code base

For example, some code that creates a pact in a consumer project might look like this:

  1. describe MyServiceProviderClient do
  2. subject { MyServiceProviderClient.new }
  3. describe "get_something" do
  4. context "when a thing exists" do
  5. before do
  6. my_service.given('a thing exists').
  7. upon_receiving('a request for a thing').
  8. with(method: 'GET', path: '/thing').
  9. will_respond_with(
  10. status: 200,
  11. headers: { 'Content-Type' => 'application/json' },
  12. body: { name: 'A small something'})
  13. end
  14. it "returns a thing" do
  15. expect(subject.get_something).to eq(SomethingModel.new('A small something'))
  16. end
  17. end
  18. context "when a thing does not exist" do
  19. before do
  20. my_service.given("a thing does not exist").
  21. upon_receiving("a request for a thing").
  22. with(method: 'get', path: '/thing').
  23. will_respond_with(status: 404)
  24. end
  25. it "returns nil" do
  26. expect(subject.get_something).to be_nil
  27. end
  28. end
  29. end
  30. end

Provider codebase

Non-Ruby applications

To allow the correct data to be set up before each interaction is replayed, you will need to create an HTTP endpoint (which may or may not actually be in the same application as your provider) that accepts a JSON document describing the state. The exact format of this document depends on whether you are using the JVM implementation, or one of the wrapped Ruby implementations (effectively everything that isn’t JVM).

The endpoint should set up the given provider state for the given consumer synchronously, and return an error if the provider state is not recognised. Namespacing your provider states within each consumer will avoid clashes if more than one consumer defines the same provider state with different data.

See the pact-provider-verifier documentation for the exact details of implementing a provider-states-setup-url.

Ruby

To define service provider states that create the right data for the provider states described above, write the following in the service provider project. (The consumer name here must match the name of the consumer configured in your consumer project for it to correctly find these provider states.)

  1. # In /spec/service_consumers/provider_states_for_my_service_consumer.rb
  2. Pact.provider_states_for 'My Service Consumer' do
  3. provider_state "a thing exists" do
  4. set_up do
  5. # Create a thing here using your framework of choice
  6. # eg. Sequel.sqlite[:somethings].insert(name: "A small something")
  7. end
  8. tear_down do
  9. # Any tear down steps to clean up your code
  10. end
  11. end
  12. provider_state "a thing does not exist" do
  13. no_op # If there's nothing to do because the state name is more for documentation purposes,
  14. # you can use no_op to imply this.
  15. end
  16. end

Require your provider states file in the pact_helper.rb

  1. # In /spec/service_consumers/pact_helper.rb
  2. require './spec/service_consumers/provider_states_for_my_service_consumer.rb'

Base state

To define code that should run before/after each interaction for a given consumer, regardless of whether a provider state is specified or not, define set_up/tear_down blocks with no wrapping provider_state.

  1. Pact.provider_states_for 'My Service Consumer' do
  2. set_up do
  3. # This will run before the set_up for provider state specified for the interaction.
  4. # eg. create API user, set the expected basic auth details
  5. end
  6. tear_down do
  7. # ...
  8. # This will run after the tear_down for the specified provider state.
  9. end
  10. end

Global state

Global state will be set up before consumer specific base state. Avoid using the global set up for creating data as it will make your tests brittle when more than one consumer exists.

  1. Pact.set_up do
  2. # eg. start database cleaner transaction
  3. end
  4. Pact.tear_down do
  5. # eg. clean database
  6. end

Testing error responses

It is important to test how your client will handle error responses.

  1. # Consumer codebase
  2. describe MyServiceProviderClient do
  3. subject { MyServiceProviderClient.new }
  4. describe "get_something" do
  5. context "when an error occurs retrieving a thing" do
  6. before do
  7. my_service.given("an error occurs while retrieving a thing").
  8. upon_receiving("a request for a thing").with(method: 'get', path: '/thing').
  9. will_respond_with(
  10. status: 500,
  11. headers: { 'Content-Type' => 'application/json' },
  12. body: { message: "An error occurred!" } )
  13. end
  14. it "raises an error" do
  15. expect{ subject.get_something }.to raise_error /An error occurred!/
  16. end
  17. end
  18. end
  19. end
  1. # Provider codebase
  2. Pact.provider_states_for 'My Service Consumer' do
  3. provider_state "an error occurs while retrieving a thing" do
  4. set_up do
  5. # Stubbing is ususally the easiest way to generate an error with predictable error text.
  6. allow(ThingRepository).to receive(:find).and_raise("An error occurred!")
  7. end
  8. end
  9. end

Including modules for use in set_up and tear_down

Any modules included this way will be available in the set_up and tear_down blocks. One common use of this to include factory methods for setting up data so that the provider states file doesn’t get too bloated.

  1. Pact.configure do | config |
  2. config.include MyTestHelperMethods
  3. end