Starting with Pact: Your First Steps into Contract Testing

Streamlining Service Integration Through Contract Testing

Aleh
Published in
8 min readApr 17, 2024

--

Have you ever tried coordinating a dinner where everyone’s preferences are accounted for without a chat group? Chaos, right? Well, in the world of software development, ensuring different services work together smoothly can also be chaotic. Why don’t we try Pact, the group chat of services?

What is Pack? Pact is a consumer-driven contract testing tool widely utilized by developers and testers who write code. Chaos, again..😃However, let’s revisit our previous analogy to make sense of it all.

Contract testing is like ensuring every dish at our hypothetical dinner complements the others perfectly. It helps you verify that interactions between multiple services (consider each service a guest with unique dietary preferences) meet agreed-upon expectations, documented as “contracts”. Why does this matter? In systems with different services, they often evolve independently, and without contract testing, it’s too easy for one service’s changes to spoil the party for everyone else.

Dinner

Pact specializes in making these complex interactions simpler and more reliable. It allows services to communicate their needs and expectations clearly and verifies that these are met, much like confirming each guest’s dish is on the menu before they arrive. This approach prevents potential conflicts and streamlines the development process, ensuring that all services can integrate seamlessly, no matter how often they change.

By the end of this article, you’ll understand how Pact differs from traditional end-to-end testing and why it’s such a game-changer for anyone working with multiple services. So, let’s set the table and start exploring the world of contract testing with Pact, ensuring that every service arrives at our development dinner party ready to play its part perfectly 🥳.

1. Understanding Contract Testing

What Exactly is Contract Testing?

Consider contract testing as the handshake agreement between services in a software system. It’s less about checking every nook and cranny of a system and more about ensuring that two systems can pass messages back and forth as agreed. It’s like ensuring two puzzle pieces fit together perfectly without needing the entire puzzle assembled.

Compared to unit testing, which isolates and verifies the smallest parts of an application, contract testing deals with the interactions between those parts at a higher integration level. While end-to-end testing examines the system’s functionality from start to finish and can be slow and complex, contract testing offers a more granular approach by focusing solely on the interactions that must work correctly, making it faster and less prone to errors caused by system complexity.

Working with multiple services, you’ve got a lot of independent services functioning like a team of chefs in a kitchen — each responsible for part of the meal. Contract testing ensures that each chef knows what to expect from the others, reducing the chances of culinary mishaps. It’s vital for maintaining peace and productivity in a fast-paced kitchen, allowing each service to evolve without constant oversight from the entire team.

2. Core Components of Pact

core components of the pact

There are 3 core components we need to be familiar with to start working with Pact. Consumer, Provider, and Contract.

A Consumer can be any application that relies on another application’s functionalities or data to perform its tasks. In the context of HTTP-based applications, the Consumer can be a front-end application calling the backend application to retrieve some data.
Similarly, in systems that operate with messaging queues, the Consumer can be a service that consumes Kafka events sent by another service.

A Provider is usually a service that offers functionality or data to other applications, typically through an API. Using our previous analogy the Provider can be a backend application sending responses to a front-end application. In environments using events, the Provider can be a service that produces Kafka events.

HTTP-Based Applications
HTTP-Based Applications
Event-Based Applications
Event-Based Applications

A Contract or Pact is a collection of agreements known as interactions.
For HTTP, interactions define a request the Consumer makes to the provider to get some data alongside a minimal expected response, which outlines the crucial parts of the response that the Consumer needs from the Provider.

For Event-Based or Message-Based applications it’s a little bit easier — it contains only a minimal expected message.

There’s also another component that deserves attention — the Pact Broker. This useful tool aids in managing and sharing pacts. We’ll discuss it in more detail later in the articles when we implement an example.

3. How does it work?

The process starts with defining interactions between a consumer (an application making requests) and a provider (the service responding to those requests). This is encapsulated in what’s known as a Pact. As previously noted, Pact is a consumer-driven testing tool, which means testing always begins with the consumer. This approach involves setting up expectations on the consumer side first and then verifying these expectations against the provider side to ensure compliance.

Consumer Part

Each contract comprises several interactions, each detailing an expected request and the minimal response needed for the consumer’s functionality.

For example: a front-end application requests a list of items from the back-end service and expects the service to return the following list of items:

[{"id": 1, "name": "item 1", "description": "description 1"},
{"id": 2, "name": "item 2", "description": "description 2"}]

The Pact specifies the method the consumer will use to fetch the list, such as the /items endpoint, along with a description of the expected items. The pact must include only a minimal set of data, allowing the provider the flexibility to alter the content as long as it fulfills the expected criteria.

Back to our dinner analogy — a guest specifies not just the dishes but the ingredients they expect in each dish so our chef is free to enhance the dish as long as the key ingredients are present, ensuring the guest’s basic expectations are met.

Pact utilizes the Pact framework integrated with unit test frameworks in the consumer’s codebase to facilitate the creation of contracts. This integration simplifies the process of generating contracts directly from tests.

Consumer Part

Here is the breakdown of the flow:

  • In the unit test, we use the Pact library to register our expected request and response with the Pact Mock Provider.
  • The Pact Mock Provider records the interactions and saves them to a file.
  • From the unit test, we send a real request to the Mock Provider.
  • The Mock Provider validates the request and, if everything checks out, sends back the registered expected response.
  • On the unit test side, we validate the response to confirm that the flow is valid.

At this point, we have clearly defined expectations from our Consumers. Let’s proceed to verify these with our Provider.

Provider Part

After the consumer tests pass, the Pact file, which details each interaction, is used to verify that the provider can indeed handle the requests as expected and deliver the correct responses. This is crucial to ensure that once deployed, the provider can meet the consumer’s requirements.

During provider verification, each request made by the consumer is sent to the provider. The response from the provider is then checked against the minimal expected response outlined in the consumer’s tests. Verification is successful if the provider’s responses include at least the essential data specified in these minimal expected responses.

Here is the breakdown of the provider verification:

  • Using Pact library we register our expected request and response with the Pact Mock Consumer.
  • Pact Mock Consumer sends a mocked request to the real running provider.
  • The Provider sends back the actual response.
  • Pact Mock Consumer validates the response to confirm that the provider can provide the consumer with a minimal set of data.

In upcoming articles, I plan to explore another crucial component of the contract: provider state. This aspect involves setting up conditions on the consumer side to simulate the provider’s environment needed to process requests successfully. During provider verification, it’s essential to replicate these conditions on the provider side to ensure they can handle the requests properly.

Finalizing

By pairing consumer tests with provider verification for each interaction, we can fully test the contract between the consumer and provider without the need to launch the services together. This process ensures that each component works as expected independently.

4. Why should I use Pact?

Now when we see how it works you may ask me “Why should I use it?”. Well, using Pact for contract testing can lead to significant cost savings.

Initially, you’ll face a learning curve as you integrate contract tests into your build pipeline, but the technical proficiency gained will pay off. Using contract tests, either in tandem with or as a replacement for traditional end-to-end integration tests, ultimately reduces long-term costs due to their efficiency and focused scope. Here is how:

  • provides rapid feedback, enabling quicker software delivery and pinpointing issues precisely
  • reduces the need for extensive environment-dependent testing, thus saving on computational resources
  • allows for testing directly on a developer’s machine, minimizing time spent on environmental test failures
  • by focusing development on parts of the API that are actually used, it optimizes development time, making the process more efficient and responsive to changes

Pact is highly effective for testing and developing integrations within organizations, especially when both consumer and provider are under active development and the same team controls them. It ensures that software changes meet contractual obligations without waiting for end-to-end tests.

However, Pact is less suitable for public APIs or when testing interactions do not involve validating request contents. It’s also not ideal for situations where you can’t control the test data or for performance testing.

With that in mind, I hope you’re eager to give it a try 😉.

Conclusion

In conclusion, integrating Pact for contract testing significantly enhances software development. It ensures thorough validation of service interactions without the extensive time and resources typically required for end-to-end tests.

By incorporating Pact into your development workflows, you can boost the reliability and robustness of your systems, streamline testing processes, and reduce costs.

I also invite you to join me in delving deeper into Pact by implementing a real application using a consumer-driven approach and integrating it into our CI/CD pipeline. Let’s explore this together — see you in the next steps!

References

Pact Documentation

Stackademic 🎓

Thank you for reading until the end. Before you go:

--

--

Hi! My name is Aleh. 10+ years of experience combined SDE, SDET and QA. Crafting code & exploring tech horizons.