PACT - Using provider state

7.5k views Asked by At

I am trying to use pact for validating the spring boot microservices. I have generated the pact file from consumer and verified it in the provider side using pact broker.

I have another use case where I need to execute some code before validating the pact file against actual service response. I read about state change URL and state change with closure to achieve it but couldnt get an example of how to achieve this. Can someone help?

My specific situation is: I have created a contract to update customer with id 1234 (First Name: test Last Name: user).

If this customer doesnt exist, then i would need to insert this data into DB by reading the first name, last name, id from the update request in pact file and additional info (city, state, phone number) through state change code.

So my question is, can i read the request data from pact file through state change instead of configuring the first name,last name and id in the verification side?

1

There are 1 answers

6
Timothy Jones On BEST ANSWER

The state change URL is a hook you create on the provider to allow Pact to tell the provider what state it should be in at the start of the test. Before each test runs, the mock consumer taps the state change URL on your provider, and tells it the name of the state the test expects.

You need to do two things:

  1. Configure a state change URL
  2. Implement the state change endpoint on the provider

Configuring the state change URL

You can configure the state change URL in the provider verification settings. For example, using the the maven plugin:

<serviceProvider>
  <name>provider1</name>
  <stateChangeUrl>http://localhost:8080/tasks/pactStateChange</stateChangeUrl>
...

Or using the Gradle provider plugin:

hasPactWith('consumer1') {
 stateChangeUrl = url('http://localhost:8080/tasks/pactStateChange')
...

Both of these tell the mock consumer to use localhost:8080/tasks/pactStateChange to change the state of the provider before each test.

Implementing the state change endpoint

The documentation linked above tells us that by default, the format of the request is a POST request of your state string and any parameters:

{ "state" : "a provider state description", "params": { "a": "1", "b": "2" } }

To use this, you implement something like the following untested code on the provider :

@RequestMapping(value = "tasks/pactStateChange", method = RequestMethod.POST)
ResponseEntity<?> stateChange(@RequestBody ProviderState state) {
   if (state.state == "no database") {
       // Set up state for the "no database" case here
   } else if state.state == "Some other state" {
       // Set up state here
   } else if  ...   // Other states go here
   ... 
   }

   return ResponseEntity.ok().build()
}

Please excuse any spring boot errors in that example - I'm not a spring boot person, but you can see the general principle.

With the state change URL, pact doesn't tell the provider any setup details. It just tells the provider the pre-agreed state string that you used in your test. This could be something like "foo exists". Then, when implementing the handler for the state change URL, you detect "foo exists", and do any explicit setup there.

if (state.state == "foo exists") {
     // do whatever you need to set up so that foo exists
    repository.clear()
    repository.insert(new Foo("arguments that foo needs",12))
}

If you'd like to know more about the intent of provider states, have a read of the wiki page on provider states.

How to do this in your specific case

You asked:

Can i read the request data from pact file through state change instead of configuring the first name,last name and id in the verification side?

You might be confused about the intention of the contract tests - each test is a combination of state and request.

So instead of using one test to say:

  • My test is to request a customer update. If the customer exists, then I expect X response, and if it doesn't, then I expect Y response

you use two tests to say:

  • When I submit an update to the customer record (in the state when the customer exists), then I expect X response.

  • When I submit an update to the customer record (in the state where the customer does NOT exist), then I expect Y response.

These tests are two separate items in your Pact contract.

The intention is not to include details of the setup in the contract. On the consumer side, your state is just a string that says something like "Customer with id=1234 exists".

On the Provider side, your state change endpoint detects that URL and creates the state as appropriate. This is usually done in a hard-coded way:

if (state == "Customer with id=1234 exists") {
  Database.Clear()
  Database.Insert(new Customer(1234, "John","Smith")) 
} else if (state == "No customers exist") { 
  Database.Clear()
}

You don't want to do this in a parameterised way by parsing the state string, because then you're creating a new complex contract between the test consumer and the provider.

The consumer tests shouldn't know anything about how to set provider state, they should just know what state is required by the test (by name only). Similarly, the provider doesn't need to know what's being tested, it just needs to know how to turn state names into actual state.