pact-ruby
Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
Top Related Projects
A library for setting up Ruby objects as test data.
Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.
Library for stubbing and setting expectations on HTTP requests in Ruby.
Cucumber for Ruby. It's amazing!
Quick Overview
Pact-Ruby is a Ruby implementation of the Pact contract testing framework. It allows developers to define and verify consumer-driven contracts between microservices, ensuring that services can communicate effectively with each other. Pact-Ruby helps catch integration issues early in the development process, promoting more reliable and maintainable distributed systems.
Pros
- Enables consumer-driven contract testing, improving the reliability of microservices
- Supports both HTTP and message-based interactions
- Integrates well with popular Ruby testing frameworks like RSpec and Minitest
- Provides a clear and expressive DSL for defining contracts
Cons
- Learning curve for teams new to contract testing concepts
- Can add complexity to the testing process, especially for smaller projects
- Requires coordination between consumer and provider teams to maintain contracts
- May have performance overhead for large test suites
Code Examples
- Defining a Pact contract for a consumer:
Pact.service_consumer "Consumer" do
has_pact_with "Provider" do
mock_service :provider do
port 1234
end
end
end
Pact.upon_interaction do
given("a user exists")
upon_receiving("a request for user information")
with(method: :get, path: "/users/1")
will_respond_with(
status: 200,
headers: { "Content-Type" => "application/json" },
body: { id: 1, name: "John Doe" }
)
end
- Verifying a Pact contract on the provider side:
Pact.provider_states_for "Consumer" do
provider_state "a user exists" do
set_up do
User.create(id: 1, name: "John Doe")
end
end
end
Pact::ProviderVerifier.new(
provider: "Provider",
provider_base_url: "http://localhost:3000",
pact_urls: ["./pacts/consumer-provider.json"]
).verify
- Using Pact in RSpec tests:
describe "User API", pact: true do
include_pact_with "Provider"
it "retrieves user information" do
user_api.get_user(1)
expect(last_response.status).to eq(200)
expect(last_json_response).to eq(id: 1, name: "John Doe")
end
end
Getting Started
-
Add Pact to your Gemfile:
gem 'pact'
-
Install the gem:
bundle install
-
Configure Pact in your test setup (e.g.,
spec_helper.rb
):require 'pact/consumer/rspec' Pact.configure do |config| config.pact_dir = 'spec/pacts' end
-
Start writing your consumer tests using the Pact DSL as shown in the code examples above.
Competitor Comparisons
A library for setting up Ruby objects as test data.
Pros of factory_bot
- Simpler setup and usage for creating test data
- More focused on object creation and less complex than contract testing
- Widely adopted in the Ruby community, especially for Rails projects
Cons of factory_bot
- Limited to object creation and doesn't provide contract testing capabilities
- May encourage overuse of factories, leading to slower tests
- Doesn't address API integration testing or consumer-driven contract testing
Code Comparison
factory_bot:
FactoryBot.define do
factory :user do
name { "John Doe" }
email { "john@example.com" }
end
end
user = FactoryBot.create(:user)
pact-ruby:
pact = Pact.service_consumer("Consumer").has_pact_with("Provider")
pact.upon_receiving("a request for user data")
.with(method: :get, path: "/users/1")
.will_respond_with(
status: 200,
body: { name: "John Doe", email: "john@example.com" }
)
While factory_bot focuses on creating objects for testing, pact-ruby is designed for contract testing between services. factory_bot is simpler to set up and use for basic object creation, but pact-ruby offers more comprehensive API testing capabilities. The choice between the two depends on the specific testing needs of the project.
Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.
Pros of VCR
- Simpler setup and usage for recording and replaying HTTP interactions
- Broader compatibility with various HTTP libraries and testing frameworks
- Easier to maintain and update existing tests
Cons of VCR
- Limited to recording and replaying HTTP interactions, not suitable for contract testing
- May not catch changes in API contracts or schemas
- Less effective for testing microservices or distributed systems
Code Comparison
VCR:
VCR.use_cassette("example_cassette") do
response = Net::HTTP.get_response(URI('http://example.com/api'))
assert_equal "200", response.code
end
Pact:
pact = Pact.service_consumer("Consumer").has_pact_with("Provider")
pact.upon_receiving("a request for example data")
.with(method: :get, path: '/api')
.will_respond_with(status: 200, body: { data: 'example' })
Summary
VCR is ideal for recording and replaying HTTP interactions in tests, making it easier to test external API dependencies. It's simpler to set up and use compared to Pact. However, Pact is more suitable for contract testing and ensuring API compatibility between services, especially in microservices architectures. While VCR focuses on replaying recorded responses, Pact allows you to define and verify contracts between consumers and providers.
Library for stubbing and setting expectations on HTTP requests in Ruby.
Pros of WebMock
- Simpler setup and usage for basic HTTP stubbing and mocking
- Broader support for various HTTP libraries beyond just REST APIs
- More flexible request matching options, including regex and custom matchers
Cons of WebMock
- Lacks contract testing capabilities provided by Pact Ruby
- Doesn't generate documentation or provide a collaborative platform for API contracts
- Limited support for complex scenarios involving multiple services or microservices
Code Comparison
WebMock example:
stub_request(:get, "https://api.example.com/users")
.to_return(status: 200, body: '{"users": []}', headers: {'Content-Type' => 'application/json'})
Pact Ruby example:
pact.service_provider("UserService") do
service_consumer "ConsumerApp"
mock_service :user_service do
port 1234
end
end
While WebMock focuses on stubbing HTTP requests, Pact Ruby is designed for contract testing between service providers and consumers. WebMock is more suitable for simple HTTP mocking scenarios, while Pact Ruby excels in ensuring API compatibility across services in a microservices architecture.
Cucumber for Ruby. It's amazing!
Pros of Cucumber
- Widely adopted and well-established in the BDD community
- Supports natural language specifications, making it accessible to non-technical stakeholders
- Extensive ecosystem with plugins and integrations for various frameworks and tools
Cons of Cucumber
- Can be slower to execute compared to unit tests or contract tests
- Requires more setup and maintenance for test scenarios and step definitions
- May lead to duplication of test code across different feature files
Code Comparison
Cucumber example:
Feature: User login
Scenario: Successful login
Given I am on the login page
When I enter valid credentials
Then I should be logged in successfully
Pact example:
pact = consumer.create_pact_with(provider)
pact.given("a user exists")
.upon_receiving("a request for user authentication")
.with(method: :post, path: "/login")
.will_respond_with(status: 200, body: { token: "valid_token" })
While Cucumber focuses on describing behavior in natural language, Pact is specifically designed for contract testing between services. Cucumber is more versatile for various types of acceptance tests, while Pact excels in defining and verifying API contracts between consumers and providers.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Pact
Define a pact between service consumers and providers, enabling "consumer driven contract" testing.
Pact provides a fluent API for service consumers to define the HTTP requests they will make to a service provider and the HTTP responses they expect back. These expectations are used in the consumer specs to provide a mock service provider. The interactions are recorded, and played back in the service provider specs to ensure the service provider actually does provide the response the consumer expects.
This allows testing of both sides of an integration point using fast unit tests.
This gem is inspired by the concept of "Consumer driven contracts". See this article by Ian Robinson for more information.
What is it good for?
Pact is most valuable for designing and testing integrations where you (or your team/organisation/partner organisation) control the development of both the consumer and the provider, and the requirements of the consumer are going to be used to drive the features of the provider. It is fantastic tool for developing and testing intra-organisation microservices.
What is it not good for?
- Testing new or existing providers where the functionality is not being driven by the needs of the consumer (eg. public APIs)
- Testing providers where the consumer and provider teams do not have good communication channels.
- Performance and load testing.
- Functional testing of the provider - that is what the provider's own tests should do. Pact is about checking the contents and format of requests and responses.
- Situations where you cannot load data into the provider without using the API that you're actually testing (eg. public APIs). Why?
- Testing "pass through" APIs, where the provider merely passes on the request contents to a downstream service without validating them. Why?
Features
- A service is mocked using an actual process running on a specified port, so javascript clients can be tested as easily as backend clients.
- "Provider states" (similar to fixtures) allow the same request to be made with a different expected response.
- Consumers specify only the fields they are interested in, allowing a provider to return more fields without breaking the pact. This allows a provider to have a different pact with a different consumer, and know which fields each cares about in a given response.
- RSpec and Minitest support for the service consumer codebase.
- Rake tasks allow pacts to be verified against a service provider codebase.
- Different versions of a consumer/provider pairs can be easily tested against each other, allowing confidence when deploying new versions of each (see the pact_broker and pact_broker-client gems).
- Autogenerated API documentation - need we say more?
- Autogenerated network diagrams with the Pact Broker
How does it work?
- In the specs for the provider facing code in the consumer project, expectations are set up on a mock service provider.
- When the specs are run, the mock service returns the expected responses. The requests, and their expected responses, are then written to a "pact" file.
- The requests in the pact file are later replayed against the provider, and the actual responses are checked to make sure they match the expected responses.
Why is developing and testing with Pact better than using traditional system integration tests?
- Faster execution.
- Reliable responses from mock service reduce likelihood of flakey tests.
- Causes of failure are easier to identify as only one component is being tested at a time.
- Design of service provider is improved by considering first how the data is actually going to be used, rather than how it is most easily retrieved and serialised.
- No separate integration environment required for automated integration tests - pact tests run in standalone CI builds.
- Integration flows that would traditionally require running multiple services at the same time can be broken down and each integration point tested separately.
Getting help
- Pact docs: docs.pact.io
- Ruby Pact wiki: github.com/pact-foundation/pact-ruby/wiki
- Slack: slack.pact.io
- Stackoverflow: ruby pact questions or general pact questions
- Twitter: @pact_up
Installation
Add this line to your application's Gemfile:
gem 'pact'
# gem 'pact-consumer-minitest' for minitest
And then execute:
$ bundle
Or install it yourself as:
$ gem install pact
Usage - an example scenario
We're going to write an integration, with Pact tests, between a consumer, the Zoo App, and its provider, the Animal Service. In the Consumer project, we're going to need a model (the Alligator class) to represent the data returned from the Animal Service, and a client (the AnimalServiceClient) which will be responsible for making the HTTP calls to the Animal Service.
In the Zoo App (consumer) project
1. Start with your model
Imagine a model class that looks something like this. The attributes for an Alligator live on a remote server, and will need to be retrieved by an HTTP call to the Animal Service.
class Alligator
attr_reader :name
def initialize name
@name = name
end
def == other
other.is_a?(Alligator) && other.name == name
end
end
2. Create a skeleton Animal Service client class
Imagine an Animal Service client class that looks something like this.
require 'httparty'
class AnimalServiceClient
include HTTParty
base_uri 'http://animal-service.com'
def get_alligator
# Yet to be implemented because we're doing Test First Development...
end
end
3. Configure the mock Animal Service
The following code will create a mock service on localhost:1234 which will respond to your application's queries over HTTP as if it were the real "Animal Service" app. It also creates a mock provider object which you will use to set up your expectations. The method name to access the mock service provider will be what ever name you give as the service argument - in this case "animal_service"
# In /spec/service_providers/pact_helper.rb
require 'pact/consumer/rspec'
# or require 'pact/consumer/minitest' if you are using Minitest
Pact.service_consumer "Zoo App" do
has_pact_with "Animal Service" do
mock_service :animal_service do
port 1234
host "..." # optional, defaults to "localhost"
end
end
end
4. Write a failing spec for the Animal Service client
# In /spec/service_providers/animal_service_client_spec.rb
# When using RSpec, use the metadata `:pact => true` to include all the pact functionality in your spec.
# When using Minitest, include Pact::Consumer::Minitest in your spec.
describe AnimalServiceClient, :pact => true do
before do
# Configure your client to point to the stub service on localhost using the port you have specified
AnimalServiceClient.base_uri 'localhost:1234'
end
subject { AnimalServiceClient.new }
describe "get_alligator" do
before do
animal_service.given("an alligator exists").
upon_receiving("a request for an alligator").
with(method: :get, path: '/alligator', query: '').
will_respond_with(
status: 200,
headers: {'Content-Type' => 'application/json'},
body: {name: 'Betty'} )
end
it "returns an alligator" do
expect(subject.get_alligator).to eq(Alligator.new('Betty'))
end
end
end
5. Run the specs
Running the AnimalServiceClient spec will generate a pact file in the configured pact dir (spec/pacts
by default).
Logs will be output to the configured log dir (log
by default) that can be useful when diagnosing problems.
Of course, the above specs will fail because the Animal Service client method is not implemented, so next, implement your provider client methods.
6. Implement the Animal Service client consumer methods
class AnimalServiceClient
include HTTParty
base_uri 'http://animal-service.com'
def get_alligator
name = JSON.parse(self.class.get("/alligator").body)['name']
Alligator.new(name)
end
end
7. Run the specs again.
Green! You now have a pact file that can be used to verify your expectations of the Animal Service provider project.
Now, rinse and repeat for other likely status codes that may be returned. For example, consider how you want your client to respond to a:
- 404 (return null, or raise an error?)
- 500 (specifying that the response body should contain an error message, and ensuring that your client logs that error message will make your life much easier when things go wrong)
- 401/403 if there is authorisation.
In the Animal Service (provider) project
1. Create the skeleton API classes
Create your API class using the framework of your choice (the Pact authors have a preference for Webmachine and Roar) - leave the methods unimplemented, we're doing Test First Develoment, remember?
2. Tell your provider that it needs to honour the pact file you made earlier
Require "pact/tasks" in your Rakefile.
# In Rakefile
require 'pact/tasks'
Create a pact_helper.rb
in your service provider project. The recommended place is spec/service_consumers/pact_helper.rb
.
See Verifying Pacts and the Provider section of the Configuration documentation for more information.
# In spec/service_consumers/pact_helper.rb
require 'pact/provider/rspec'
Pact.service_provider "Animal Service" do
honours_pact_with 'Zoo App' do
# This example points to a local file, however, on a real project with a continuous
# integration box, you would use a [Pact Broker](https://github.com/pact-foundation/pact_broker) or publish your pacts as artifacts,
# and point the pact_uri to the pact published by the last successful build.
pact_uri '../zoo-app/spec/pacts/zoo_app-animal_service.json'
end
end
3. Run your failing specs
$ rake pact:verify
Congratulations! You now have a failing spec to develop against.
At this stage, you'll want to be able to run your specs one at a time while you implement each feature. At the bottom of the failed pact:verify output you will see the commands to rerun each failed interaction individually. A command to run just one interaction will look like this:
$ rake pact:verify PACT_DESCRIPTION="a request for an alligator" PACT_PROVIDER_STATE="an alligator exists"
4. Implement enough to make your first interaction spec pass
Rinse and repeat.
5. Keep going til you're green
Yay! Your Animal Service provider now honours the pact it has with your Zoo App consumer. You can now have confidence that your consumer and provider will play nicely together.
Using provider states
Each interaction in a pact is verified in isolation, with no context maintained from the previous interactions. So how do you test a request that requires data to already exist on the provider? Read about provider states here.
Configuration
See the Configuration section of the documentation for options relating to thing like logging, diff formatting, and documentation generation.
Pact best practices
As in all things, there are good ways to implement Pacts, and there are not so good ways. There are also some Pact GOTCHAS to beware of! Check out the Best practices section of the documentation to make sure you're not Pacting it Wrong.
Current Pact specification version
Currently, Ruby Pact supports writing Pacts in v2, and verifying Pacts in v3 format, HOWEVER it only supports the rules that were defined in v2 (like
and term
). If you are interested in helping add support for the v3 rules, please talk to @Beth in the #pact-ruby
channel on our Slack.
Docs
- Example
- Configuration
- Terminology
- Provider States
- Verifying pacts
- Sharing pacts between consumer and provider
- Regular expressions and type matching with Pact
- Frequently asked questions
- Rarely asked questions
- Best practices
- Troubleshooting
- Testing with pact diagram
- News, blogs, videos and articles
Related libraries
Pact Provider Proxy - Verify a pact against a running server, allowing you to use pacts with a provider of any language.
Pact Broker - A pact repository. Provides endpoints to access published pacts, meaning you don't need to use messy CI URLs in your codebase. Enables cross testing of prod/head versions of your consumer and provider, allowing you to determine whether the head version of one is compatible with the production version of the other. Helps you to answer that ever so important question, "can I deploy without breaking all the things?"
Pact Broker Client - Contains rake tasks for publishing pacts to the pact_broker.
Shokkenki - Another Consumer Driven Contract gem written by one of Pact's original authors, Brent Snook. Shokkenki allows matchers to be composed using jsonpath expressions and allows auto-generation of mock response values based on regular expressions.
A list of Pact implementations in other languages - JVM, .Net, Javascript and Swift
Links
Simplifying microservices testing with pacts - Ron Holshausen (one of the original pact authors)
Integrated tests are a scam - J.B. Rainsberger
Consumer Driven Contracts - Ian Robinson
Integration Contract Tests - Martin Fowler
Roadmap
See ROADMAP.md.
Contributing
See CONTRIBUTING.md.
Contributors
This project exists thanks to all the people who contribute. [Contribute].
Backers
Thank you to all our backers! ð [Become a backer]
Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]
Top Related Projects
A library for setting up Ruby objects as test data.
Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests.
Library for stubbing and setting expectations on HTTP requests in Ruby.
Cucumber for Ruby. It's amazing!
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot