Writing Contract Tests with Pact in Spring Boot
Tags: microservices, engineering, contracttesting
Published: 2020-08-13 08:39:58



Written by Kristine Jetzke

*This blog post is part 2 of a series on consumer-driven contract testing. The other parts are: [introduction](https://kreuzwerker.de/blog/introduction-to-consumer-driven-contract-testing "Introduction"), [integrating contract tests into build pipelines](https://kreuzwerker.de/blog/integrating-contract-tests-into-build-pipelines-with-pact-broker-and) and [lessons learned](https://kreuzwerker.de/blog/contract-tests-lessons-learned "lessons learned"). You can find a short explanation how to use Junit5 instead of Junit4 [HERE](https://kreuzwerker.de/post/migrating-pact-contract-tests-from-junit4-to-junit5 "Blog Post").*

This blog post is part 2 of our blog post series on consumer-driven contract testing with Pact. See [part 1]((https://kreuzwerker.de/blog/introduction-to-consumer-driven-contract-testing "Introduction") for an introduction to contract testing. This part will explain how consumer and provider tests are written with [Pact JVM](https://docs.pact.io/implementation_guides/jvm "Pact JVM") in a Spring Boot environment. 

Pact was initially written for Ruby but is now available for [many different languages](https://docs.pact.io/implementation_guides "many different languages") e.g. JavaScript, Python and Java. In this post we will use Java 8, Junit 4, Maven and Spring Boot because those were the primary technologies used at our client. 

In Pact, the contract itself is called a *pact*. It is a JSON file that contains *interactions*. An interaction consists of the request and the expected response. The expected response contains the expected JSON but only the parts that are actually relevant for the consumer. 

#### Example participants

We will use the following participants - or *pacticipants* as they are called in Pact - throughout this blog post series:

The provider is a user service which has the endpoint `GET /users/{userId}` which returns a user’s data in the following form:

```json
{
  "id": "1",
  "legacyId": 123456,
  "name": "Beth Miller",
  "role": "ADMIN",
  "lastLogin": "2018-10-16T14:34:12.000Z",
  "friends": [
    {
      "id": 2,
      "name": "Ronald Smith "
    },
    {
      "id": 736,
      "name": "Matt Spencer"
    }
  ]
}
```

   
The [consumer](https://kreuzwerker.de/blog/introduction-to-consumer-driven-contract-testing "Introduction") [^1] is a messaging app that shows the user’s name. It expects that a call to `GET users/{userId}` returns

- a 200 success code,
- content type JSON with UTF-8 encoding and
- a JSON body that contains the field `name` of type string

All the code can be found in [this](https://github.com/tinexw/cdc-with-pact "this") GitHub repository.

#### Consumer: Creating the contract

The easiest way to create the Pact file is via a unit test. The test goes in the same directory as all the other unit tests of the consumer. 

The unit test will do two things: It verifies that our code can handle the expected provider responses and - as a nice side effect - it creates the Pact file. 

One of the first questions is: Which code should be tested in order to verify that the expected provider response can be handled? A good starting point is the class that directly interacts with the provider. In our example, we have a `UserServiceClient` that provides a `getUser` method. This method calls the user service via a `RestTemplate`, parses the response into a `User` object and returns it:

```java

public class UserServiceClient {

  private final RestTemplate restTemplate;

  public UserServiceClient(@Value("${user-service.base-url}") String baseUrl) {

    this.restTemplate = new RestTemplateBuilder().rootUri(baseUrl).build();

  }
  public User getUser(String id) {

    return restTemplate.getForObject("/users/" + id, User.class);

  }
}
public class User {
  private String name;
  // Getter + setter if needed
}
```

   
The unit test will perform the following steps:

1. Start a server that mocks the provider with the given interactions.

2. Call the `getUser` method which will call the mocked provider.

3. Assert the returned `User` object.

4. Write the Pact file based on the given interactions.

The interactions will be defined in a separate method, annotated with `@Pact`. Steps 1. and 4. will be handled by the Pact framework’s `PactProviderRuleMk2` Junit rule and steps 2. and 3. will be in a regular `@Test` method.

As a first step we create the unit test class and add the test method:

```

@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,
  properties = "user-service.base-url:http://localhost:8080",
  classes = UserServiceClient.class)
public class UserServiceContractTest {
  @Autowired
  private UserServiceClient userServiceClient;
  @Test
  public void userExists() {
    User user = userServiceClient.getUser("1");
    assertThat(user.getName()).isEqualTo("user name for CDC");
  }
}
```

    
Note: One of the most challenging parts is which user ID to use. For now, we just assume that a user with ID `1` exists in the provider. We will come back to this question later on.

As a next step, we will define the interactions by creating a method annotated with `@Pact` and the consumer name. The method returns the description of the contract using the [pact-jvm Lambda DSL](https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-java8 "pact-jvm").

   ```

   @Pact(consumer = "messaging-app")

   public RequestResponsePact pactUserExists(PactDslWithProvider builder) {

     return builder.given("User 1 exists")
       .uponReceiving("A request to /users/1")
       .path("/users/1")
       .method("GET")
       .willRespondWith()
       .status(200)
       .body(LambdaDsl.newJsonBody((o) -> o
         .stringType("name", “user name for CDC”)
        ).build())
       .toPact();
   }
```

    
Note: The meaning of `given`(...) and `uponReceiving`(...) was quite confusing for us. `uponReceiving`(...) is just the description of the contract. `given`(...) can be used to prepare the provider i.e. bring it into a certain [state](https://docs.pact.io/getting_started/provider_states "state"). More on this later.

Note: It is very important to use `stringType` instead of `stringValue` even though `stringValue` is used in the pact-jvm documentation. `stringType` generates a matcher that just checks the type whereas `stringValue` puts the concrete value into the contract. It might be tempting to use the real user name since we know what it is. However, this again leads to a tight coupling between the consumer and provider. (If the name changes, the tests will fail.) Note that `user name for CDC` is just an example value that is returned by the mocked server. It’s not strictly necessary to set it. We just use it to assert that the client parses the response correctly.

The last step is setting up the mock server. This is done by adding the `PactProviderRuleMk2` rule to the class and annotating the test method with `PactVerification` and the name of the previous method. This annotation is important because it tells the Pact provider rule to start the mock server with the interaction defined in the given method (which is called fragment here for some reason). 

Note: The rule was quite confusing for us as well. It does many things and has a weird name. Most importantly it starts a mock server that will return the expected response and writes the Pact JSON file at the end.

The complete class now looks like this:

```

@RunWith(SpringRunner.class)

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE,

  properties = "user-service.base-url:http://localhost:8080",
  classes = UserServiceClient.class)
public class UserServiceContractTest {
    
  @Rul
  public PactProviderRuleMk2 provider = new PactProviderRuleMk2("user-service", null,     
    8080, this);
  @Autowired
  private UserServiceClient userServiceClient;
  @Pact(consumer = "messaging-app")
  public RequestResponsePact pactUserExists(PactDslWithProvider builder) {
    return builder.given("User 1 exists")
      .uponReceiving("A request to /users/1")
      .path("/users/1")
      .method("GET")
      .willRespondWith()
      .status(200)
      .body(LambdaDsl.newJsonBody((o) -> o
        .stringType("name", “user name for CDC”)
       ).build())
      .toPact();
  }
  @PactVerification(fragment = "pactUserExists")
  @Test
  public void userExists() {
    final User user = userServiceClient.getUser("1");
    assertThat(user.getName()).isEqualTo("user name for CDC");
  }
}
```

Note: In our real setup we obviously don’t use port 8080 for the mocked server because we want to run tests in a CI environment where this port might be in use. Thus, we created [our own JUnit rule](https://github.com/tinexw/cdc-with-pact/blob/master/messaging-app/src/test/java/de/kreuzwerker/cdc/messagingapp/RandomPortRule.java "our own JUnit rule") that finds an open port and stores it into an environment variable so that it can be [used in the Spring properties](https://github.com/tinexw/cdc-with-pact/blob/master/messaging-app/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTest.java#L29 "used in the Spring properties"). 

Now if we run the test it will generate the following file `&lt;consumer-name>-&lt;provider-name>.json` in the folder `target/pacts`:

```

{
    "provider": {
        "name": "user-service"
    },
    "consumer": {
        "name": "messaging-app"
    },
    "interactions": [
        {
            "description": "A request to /users/1",
            "request": {
                "method": "GET",
                "path": "/users/1"
            },
            "response": {
                "status": 200,
                "headers": {
                    "Content-Type": "application/json; charset=UTF-8"
                },
                "body": {
                    "name": "user name for CDC"
                },
                "matchingRules": {
                    "body": {
                        "$.name": {
                            "matchers": [
                                {
                                    "match": "type"
                                }
                            ],
                            "combine": "AND"
                        }
                    },
                    "header": {
                        "Content-Type": {
                            "matchers": [
                                {
                                    "match": "regex",
                                    "regex": "application/json;\\s?charset=(utf|UTF)-8"
                                }
                            ],
                            "combine": "AND"
                        
                    }
                }
            },
            "providerStates": [
                {
                    "name": "User 1 exists"
                }
            ]
        
    ],
    "metadata": {
        "pactSpecification": {
            "version": "3.0.0"
        },
        "pact-jvm": {
            "version": "3.5.24
        }
    }
}
```

The metadata section and the `Content-Type` are added automatically by the Pact framework.

It’s easy to extend the contract by including more attributes in the unit test:

- `lastLogin` in a specific date format
- `role` has to be either ADMIN or USER (we will come back later to the question if this is a good idea)
- `friends` has to be an array with a minimum size of 0. The array is expected to contain object entries with string attributes `id` and `name`. Note that the 2 is not used in the contract. It just tells the mocked server how many array elements to return in the mocked response.

    
 ```   
LambdaDsl.newJsonBody((o) -> o
  .stringType("name", "user name for CDC")
  .timestamp("lastLogin", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", new Date(1539693252000L))
  .stringMatcher("role", "ADMIN|USER", "ADMIN")
  .minArrayLike("friends", 0, 2, friend -> friend
    .stringType("id", "2")
    .stringType("name", "a friend")
  )).build();
```

This will result in a Pact file with rules for all the given fields. The above example shows only some of the available methods to specify the expected body. More methods can be found in the [pact-jvm documentation](https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-junit#dsl-matching-methods "pact-jvm documentation").

Now we can move on to verifying this test on the provider side.

#### Provider: Verifying the contracts

We will now show how to create the tests on the provider site that verify that the contracts are fulfilled. We use Spring Boot integration tests for this because it allows us to 

- easily mock away any downstream systems we don’t want to depend on e.g. a database and
- to run the tests as part of our normal build.[^3] 

The controller for  `GET /Users/{userId}` looks like this:

```json

@RestController
public class UserController {
  private final UserService userService;
  public UserController(UserService userService) {
    this.userService = userService;
  }
  @GetMapping("/users/{userId}")
  public User getUser(@PathVariable String userId) {
    return userService.findUser(userId);
  }
}

```

    
The `userService` just returns a dummy user for now but usually it loads the user from somewhere e.g. a database. 

    
```json

@Service
public class UserService {
    
  public User findUser(String userId) {
    return User.builder()
      .id(userId)
      .legacyId(UUID.randomUUID().toString())
      .name("Beth")
      .role(UserRole.ADMIN)
      .lastLogin(new Date())
      .friend(Friend.builder().id("2").name("Ronald Smith").build())
      .friend(Friend.builder().id("3").name("Matt Spencer").build())
      .build();
  }
}
```

For the test we create a regular Spring Boot web integration test and use the `SpringRestPactRunner` Junit runner.

Additionally, we need to add the following annotations to the class:

- Which Pact files to load by specifying the `@Provider` annotation with the provider name.
- Where to load the Pact files from by specifying one of `@PactBroker` or `@PactFolder`[^2]. We will use `@PactFolder` for now to load the files from the file system because it’s the easiest way to get started. Thus, we create a `pacts` directory and copy the Pact file created by the consumer to it. (We will show how the Pact Broker can be used instead in the next part of this blog post series.)

Inside the class we specify:

- The *target*: Where to run the interactions against and verify the responses. The `SpringBootHttpTarget` is for the Spring Boot integration tests. The tests are executed against the application started by the integration test on the random port. There are other targets e.g. `MockMvcTarget` which we have successfully used in a plain spring application where we run the test with just the controller.
- A method for each provider state given in the contract. The method can be used to set up the desired provider state e.g. creating the user in the database or mocking the service provides the user.

```json

@RunWith(SpringRestPactRunner.class)
@Provider("user-service")
@PactFolder("pacts")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ContractTest {
  @TestTarget
  public final Target target = new SpringBootHttpTarget();
  @State("User 1 exists")
  public void user1Exists() {
    // nothing to do, real service is used
  }
}
```

If we run the test now it passes and outputs the following:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9m0238aeys1t79525ude.png)


#### Working with the contract

In this section we will go through several scenarios that can happen in the lifecycle of an API and show how Pact will behave.

__Provider adds a field to the API__

The provider adds a new field to the API. Nothing will happen, everything will still pass because the consumer only cares about the attributes in its contract.

__Provider deletes a field from the API__

Removal of an unused field: The provider decides to remove the `legacyId` field from the API. They can just do it because the consumer does not expect it to be part of the response. The contract test still passes.

Removal of a used field: The provider decides to rename the `name` field to `fullname` (which is the same as removing the name field from the contract’s perspective):

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zzvufzhj3ke8a2cpbw9n.png)

The provider test fails because the consumer’s contract is violated since it expects the name field.

__Provider changes date format__

The provider changes the date format from an ISO-8601 date to a timestamp. (This actually happens in reverse when [upgrading from Spring Boot 1 to 2].)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zzvufzhj3ke8a2cpbw9n.png)

The provider test fails because the consumer’s contract is violated since it expects a different format.

__Provider introduces new enum__

Another consumer requests that the provider adds a new user role `SUPER_ADMIN`. The provider adds it and the user with ID `1` (used in the consumer test) gets this new role assigned. The test now fails with:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vmiek7zrft2iqw6uoe52.png)

Whether this is good or bad depends on the consumer. If the consumer cannot handle anything else except `USER` and `ADMIN` it’s good that the provider is now aware. On the other hand, it somewhat happened accidentally that the provider even noticed. If the user with ID `1` would have kept its role, the test would have passed. In general, it’s the same as with fields: the consumer should be able to handle additional information. If the consumer really needs to distinguish between `USER` and `ADMIN`, it should have created two interactions with two different states, one for `ADMIN` role and one for `USER` role.

__Consumer breaks own implementation__

Someone new in the consumer team wants to rename `name` to `fullname`. They refactor the class accordingly. This assertion in the unit test will now fail:

    assertThat(user.getFullname()).isEqualTo("user name for CDC");

Because the response from the mocked server does not contain the `fullname` field (just the `name` field).

__Consumer adds a new interaction (and new provider state)__

The consumer wants to ensure that a 404 is returned if the user does not exist. A new Pact definition is added to the consumer unit test. 

```json

  @Pact(consumer = "messaging-app")
  public RequestResponsePact pactUserDoesNotExist(PactDslWithProvider builder) {
    return builder.given("User 2 does not exist")
      .uponReceiving("A request to /users/2")
      .path("/users/2")
      .method("GET")
      .willRespondWith()
      .status(404)
      .toPact();
    }
  @PactVerification(fragment = "pactUserDoesNotExist")
  @Test
  public void userDoesNotExist() {
    expandException.expect(HttpClientErrorException.class);
    expandException.expectMessage("404 Not Found");
    userServiceClient.getUser("2");
  }
```

After running the test the generated Pact file contains two interactions:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rdu0asdez8rt5ruwsp87.png)


The provider test will now execute and verify both interactions against the Spring Boot application sequentially. The test for the new interaction fails because the requested provider state does not exist. But only adding the state will still result in a failure because we did not yet add any real state setup -  we always return user with ID `1`.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kbq8lkmbzispt09dm2ov.png)

This is a real drawback of using the Spring Boot integration tests. There is still a dependency between the consumer and the provider: Whenever the consumer needs a new state, the provider code needs to be updated first. 

A good solution is to provide more generic provider states that accept parameters e.g. in this case we can provide a parameter `userExists`. If it’s set to `true`, the user service (or the underlying source) will be prepared/mocked so that a user is always returned. If it’s set to `false`, the user service will be set up in such a way that the controller will return a 404.

The first step is to define a generic provider state instead of concrete states in the provider test:

  ```json
  @State("default")
  public void toDefaultState(Map&lt;String, Object> params) {
    final boolean userExists = (boolean) params.get("userExists");
      if (userExists) {
      // set up user service to return a user
    } else {
      // set up user service to return no user
    }
```

The consumer tests can now be rewritten to use the new state and pass `true` respectively `false`:

    

```json
@Pact(consumer = "messaging-app")
public RequestResponsePact pactUserExists(PactDslWithProvider builder) {
  return builder.given("default", Collections.singletonMap("userExists", true))
    .uponReceiving("A request for an existing user")
    ...
}
```
and 
```json

@Pact(consumer = "messaging-app")

public RequestResponsePact pactUserDoesNotExist(PactDslWithProvider builder) {

  return builder.given("default", Collections.singletonMap("userExists", false))

    .uponReceiving("A request for a non-existing user")

    ...

}

```

__Consumer needs a new field__

The consumer would like to have a new field `nickname` in the response. They change their unit test, generate the new Pact file and pass it to the provider. The provider test will fail until they add the new field. We will show in the next part of this blog post series what a workflow for changes can look like and how it can be integrated into the build pipeline.

#### Lessons

In the previous sections we showed how to create tests for the consumer and provider and how they help in detecting breaking changes in an API. However, the more you use those tests the more you realize that the devil is in the detail. Here are three of our most important lessons:

__Mock as little as possible on the provider site__

Have you noticed how we skimmed over the user creation in the previous section? It turns out that it’s actually not that obvious where those users come from. In one provider we initially mocked the service layer and returned a mocked user. However, this mocked user always had all possible fields set. So the contract tests kept passing even when one of the fields that the consumer expected was never set by the real service anymore (because the users in the database never had that field anymore). That’s why we later chose to mock as far back as possible - the database layer (using the real database was not possible for various reasons). 

__Be strict on the consumer site__

We have a consumer in a legacy system where the communication with the provider is similar to the `UserServiceClient` in the example above -  a Rest Template that just parses the response. 

We have had several discussions about whether it’s sufficient to use this class in the contract test or not. Why? If the provider stops sending a field, the class just parses it as null value and throws no error. However, a lot of the fields are mandatory and the service layer throws a runtime exception if they are missing. Since the service layer is not part of the contract test, these exceptions are never detected. In the end we agreed that it would be better to include some parts of the service layer in the contract test.

For a new consumer we wrote the client in a way that it additionally validates the response. In this way exceptions are thrown by the client itself and the service layer can rely on mandatory fields being present.

__Echo input back instead of testing functionality__

We only covered a simple GET request so far where the consumer only cared about the endpoint name and the response object. It’s not that straightforward with non-simple GET operations. 

Let’s say we have an endpoint which returns a list with a limited number of users:

`GET /users?limit={limit}`

The consumer wants to ensure that the provider accepts the `limit` parameter. It’s not sufficient to just write a contract that passes the parameter and expects a successful response. Why? Because if the provider removes the parameter from the controller, it will still return a successful response. Spring Boot by default just ignores any unknown query parameters.

Of course the consumer could just write a test that passing a limit of 5 will result in 5 users being returned. But the consumer actually does not want to test the internal logic of the provider. This should be covered by the provider’s own functional tests. You could argue that it’s no big deal to ensure that exactly 5 users are returned. But image a sorting parameter instead with a complicated algorithm behind it - do you really want to re-implement that sorting logic in every single consumer?

The [approach recommended by Pact][21] is to simply echo the query parameters back to the consumer and only fall back to functional testing if that’s really not possible.

For example the provider could respond like this for the request `GET/users?limit=5&foo=bar`:

```json
{
  “query”: {
    “limit”: 5
  },
  “users:” [
   // users
  ]
}
```

The unknown query parameter *foo* is not echoed back.

The same approach can be used for methods that pass a payload: return the parsed payload and - only if that’s not possible - check the desired outcome. 

#### Conclusion

Setting up Pact and writing the initial tests is very easy. Solving all the nitty-gritty details so that everyone really trusts those tests is difficult. But once you have, you can replace all those awful, brittle, end-to-end tests and never look back.

In the next part of this blog post series we will show how contract verification can be integrated into the build pipeline so that any breaking change is automatically detected before it gets deployed.


## Notes

[^1]: We will only use one consumer in this example because it’s sufficient to explain the concepts. In real life you obviously have more than one. If you don’t, you might not gain anything from using Pact.

[^2]: A different approach is to run the tests against the running services. We chose this approach for some of the legacy services where it was not easily possible to add Spring Boot integration tests. We used the verify goal of the [pact maven plugin][23]. The downsides of this approach are: a)  the tests depend on the service being up and running, b) they can only run after the service was deployed i.e. it is more difficult to run the contract tests before deploying to production, in a branch or locally and c) it only works if the service either provides an endpoint to create test data or if it returns very stable resources.

[^3]: There are some other options like `@PactUrl` or `@VersionedPactUrl` to load the files from a URL and it’s also possible to define custom loaders.

***

[Source Pact Logo](https://raw.githubusercontent.com/pact-foundation/pact-logo/master/media/logo-black.png)

[Source Spring Boot Logo](https://brandfolder.com/pivotal-assets)

[1]: https://kreuzwerker.de/blog/introduction-to-consumer-driven-contract-testing "Introduction"

[2]: https://kreuzwerker.de/blog/integrating-contract-tests-into-build-pipelines-with-pact-broker-and

[3]: https://docs.pact.io/implementation_guides/jvm "Pact JVM"

[4]: https://docs.pact.io/implementation_guides "many different languages"

[5]: #f1

[6]: https://github.com/tinexw/cdc-with-pact "this"

[7]: https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-java8 "pact-jvm"

[8]: https://docs.pact.io/getting_started/provider_states "state"

[9]: https://github.com/tinexw/cdc-with-pact/blob/master/messaging-app/src/test/java/de/kreuzwerker/cdc/messagingapp/RandomPortRule.java "our own JUnit rule"

[10]: https://github.com/tinexw/cdc-with-pact/blob/master/messaging-app/src/test/java/de/kreuzwerker/cdc/messagingapp/UserServiceContractTest.java#L29 "used in the Spring properties"

[11]: https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-consumer-junit#dsl-matching-methods "pact-jvm documentation"

[12]: #f2

[13]: #f3

[14]: //images.ctfassets.net/hqu2g0tau160/4siuv3OicMUG6aAiEIueMs/1abfe0467afb2ff32707ee32f2596754/Picture1.png

[15]: //images.ctfassets.net/hqu2g0tau160/3OLdIpUYqk6uowICqkY0o0/14373333834adf051c08ee9e33f9ea1d/Picture2.png

[16]: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide#jackson--json-support "upgrading from Spring Boot 1 to 2"

[17]: //images.ctfassets.net/hqu2g0tau160/stFzWk2lIkO26ywaE6WoW/c28488002c76297e8f04e12aa4fb3ff1/Picture3.png

[18]: //images.ctfassets.net/hqu2g0tau160/7awZ51yLTicYKqgUkycikm/f7db496b405f0a1df10058e72c338423/Picture4.png

[19]: //images.ctfassets.net/hqu2g0tau160/kZ6v2BI5wWkCU6WQ8M6sa/20da498e3c4e178b44b72bd932a3e425/Picture5.png

[20]: //images.ctfassets.net/hqu2g0tau160/5bvnJMpioogaYqye4KwiwM/c58e2fc9a8c6beb414776daf1203469f/Picture6.png

[21]: https://docs.pact.io/best_practices/provider/using_provider_states_effectively "approach recommended by Pact"

[22]: #a1

[23]: https://github.com/DiUS/pact-jvm/tree/master/pact-jvm-provider-maven "pact maven plugin"

[24]: #a2

[25]: #a3

[26]: https://raw.githubusercontent.com/pact-foundation/pact-logo/master/media/logo-black.png "Source Pact Logo"

[27]: https://brandfolder.com/pivotal-assets "Source Spring Boot Logo"