Kretar

Freelance Java Consultant

Join the Pact

At my last couple of clients we started to use API testing to separate the individual components. I’ve looked at a number of frameworks that try to help you use “Consumer Driven Contract Testing”. My introduction to CDCs was the blog post of Martin Fowler and another one by Ian Robinson.

The obvious followup was looking at ThroughWorks Pacto. Unfortunately at that time, I ran into a language conflict. Futhermore, Thoughtworks more recently decided to suspend work on Pacto and refers to projects like Pact.

As one of my clients had a strong Spring bases, we also took a look at Spring Cloud Contract. Although it is a perfect help for building contracts, they seem to focus the contract on the provider side instead of the consumer. They suggest to define stubs for the providers behaviour which the consumers can then use to verify if they’re still inline with expected behaviour.

In the end at both clients we decided to use Pact and I’ll try to show you the steps needed to get this working. First step is to let the consumer of the API define what pieces of the API are being used. The fluent interface makes it reasonable intuitive how to define behaviour.

public class ContractTest {
    @Pact(consumer = "consumer")
    public RequestResponsePact createFragment(PactDslWithProvider builder) {
        return builder
                .given("Known pets")
                .uponReceiving("A request for an unknown pet")
                    .method("GET")
                    .path("/pets/" + UNKNOWN_PET_ID)
                .willRespondWith()
                    .status(HttpStatus.NOT_FOUND.value())
                    .body("Pet not found")
                .uponReceiving("A request for the first page of pets")
                    .method("GET")
                    .path("/pets/" + KNOWN_PET_ID)
                .willRespondWith()
                    .status(HttpStatus.OK.value())
                    .body(LambdaDsl.newJsonBody(a -> {
                        a.stringType("id");
                        a.object("category", c -> {
                            c.stringType("id");
                            c.stringType("name");
                        });
                        a.stringType("name");
                        a.array("photoUrls", urls -> {});
                        a.array("tags", tags -> {
                            tags.object(tag -> {
                                tag.stringType("id");
                                tag.stringType("name");
                            });
                        });
                        a.stringType("status");
                    }).build())
                .toPact();
    }
}

To verify if the consumer conforms to this pact, we can write a unit test that calls the client code.

@Test
@PactVerification(value="petstore_api", fragment="createFragment")
public void it_should_find_pet_by_id() {
    PetService petService = new PetService(petstoreApi.getUrl());
    assertFalse(petService.findById(UNKNOWN_PET_ID).isPresent());
    assertTrue(petService.findById(KNOWN_PET_ID).isPresent());
}

We’ll need to specify which provider we want to cover. We do so by providing a JUnit @Rule as a field.

@Rule 
public PactProviderRuleMk2 petstoreApi = new PactProviderRuleMk2("petstore_api", PactSpecVersion.V3, this);

Of course we’ll need to add a dependency for it.

<dependency>
    <groupId>au.com.dius</groupId>
    <artifactId>pact-jvm-consumer-java8_2.12</artifactId>
    <version>3.5.13</version>
    <scope>test</scope>
</dependency>

And there you have it. This should give you green lights. You have tested your service API by defining the behaviour you’re expecting of a remote API. The next step is to publish this contract to a central repository so all providers can verify if they are and remain compatible with their consumers. The creators of Pact have provided us with a specific repository that not only accepts and stores these contracts, they are also browsable and you can find interesting dependencies between your services. For a first trial you can easily start one by running this docker-compose config.

version: '3'
services:
  broker:
    image: dius/pact-broker
    ports:
      - "4568:80"
    environment:
    - PACT_BROKER_DATABASE_ADAPTER=mysql2
    - PACT_BROKER_DATABASE_HOST=mysql 
    - PACT_BROKER_DATABASE_NAME=pact
    - PACT_BROKER_DATABASE_USERNAME=root
  mysql:
    image: mysql
    ports:
      - "3306"
    environment:
    - MYSQL_ALLOW_EMPTY_PASSWORD=true
    - MYSQL_DATABASE=pact

There is a maven plugin available to let you upload the contracts. Add this to your project to upload it as part of your build.

<plugin>
    <groupId>au.com.dius</groupId>
    <artifactId>pact-jvm-provider-maven_2.12</artifactId>
    <version>3.5.13</version>
    <configuration>
        <pactBrokerUrl>http://localhost:4568</pactBrokerUrl>
    </configuration>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>publish</goal>
            </goals>
        </execution>
    </executions>
</plugin>

So far, so good. This was all straight forward to figure out and to get to work. For me the problem started when I tried to verify this contract at the providers side. There are a number of Maven plugins available for the provider tests. It’s up to you to find the one most suitable to your situation. I started with the JUnit approach.

<dependency>
    <groupId>au.com.dius</groupId>
    <artifactId>pact-jvm-provider-junit_2.12</artifactId>
    <version>3.5.13</version>
    <scope>test</scope>
</dependency>

Having the dependency in place, I could start my test class. In fact you’re not really writing a test here, but you’re defining the behaviour of your service. For the test to be able to verify this behaviour, you will have to make sure you have an API to test against. First order business therefore, is to start your API and make it available at a known location.

private static ConfigurableApplicationContext context;

@BeforeClass
public static void startService() {
    context = new SpringApplicationBuilder().profiles("pact-test").sources(Application.class, PactTest.class).run();
}

@TestTarget
public final Target target = new HttpTarget(8084);

@AfterClass
public static void kill() {
    context.stop();
}

Obviously you’ll need some reference to the central contract repository. We can annotate the class to make it work.

@RunWith(PactRunner.class)
@Provider("petstore_api")
@PactBroker(host="localhost",port="4568")
@Configuration
@Profile("pact-test")
public class PactTest {
    …
}

All that’s left is to make sure that your service acts like it is in the state the consumer is expecting. For this example it means it recognises the pet IDs that should and should not be known. The specific “magic” ids are defined inside the contract and are passed into your state definition as arguments.

@Bean
public PetRepository petRepository() {
    return Mockito.mock(PetRepository.class);
}

@State("Known pets")
public void knownPets(Map<String, Object> data) {
    PetRepository petRepository = context.getBean("petRepository", PetRepository.class);
    String knownPetId = data.get("KNOWN_PET_ID").toString();
    
    when(petRepository.findById(knownPetId)
        .thenReturn(buildOptionalPet(knownPetId));
}

As you can see I choose to mock my repositories to make sure I capture as much behaviour as possible. This way I can assert marshalling techniques, endpoint mapping and the potential merging of objects.

And there you have it. You can find the code at GitHub.