• Stars
    star
    501
  • Rank 88,002 (Top 2 %)
  • Language
    Python
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Python version of Pact. 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.

pact-python

slack License Build and Test

Python version of Pact. 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. Currently supports version 2 of the Pact specification.

For more information about what Pact is, and how it can help you test your code more efficiently, check out the Pact documentation.

Note: As of Version 1.0 deprecates support for python 2.7 to allow us to incorporate python 3.x features more readily. If you want to still use Python 2.7 use the 0.x.y versions. Only bug fixes will now be added to that release.

How to use pact-python

Installation

pip install pact-python

Getting started

A guide follows but if you go to the examples. This has a consumer, provider and pact-broker set of tests for both FastAPI and Flask.

Writing a Pact

Creating a complete contract is a two step process:

  1. Create a test on the consumer side that declares the expectations it has of the provider
  2. Create a provider state that allows the contract to pass when replayed against the provider

Writing the Consumer Test

If we have a method that communicates with one of our external services, which we'll call Provider, and our product, Consumer is hitting an endpoint on Provider at /users/<user> to get information about a particular user.

If the code to fetch a user looked like this:

import requests


def user(user_name):
    """Fetch a user object by user_name from the server."""
    uri = 'http://localhost:1234/users/' + user_name
    return requests.get(uri).json()

Then Consumer's contract test might look something like this:

import atexit
import unittest

from pact import Consumer, Provider


pact = Consumer('Consumer').has_pact_with(Provider('Provider'))
pact.start_service()
atexit.register(pact.stop_service)


class GetUserInfoContract(unittest.TestCase):
  def test_get_user(self):
    expected = {
      'username': 'UserA',
      'id': 123,
      'groups': ['Editors']
    }

    (pact
     .given('UserA exists and is not an administrator')
     .upon_receiving('a request for UserA')
     .with_request('get', '/users/UserA')
     .will_respond_with(200, body=expected))

    with pact:
      result = user('UserA')

    self.assertEqual(result, expected)

This does a few important things:

  • Defines the Consumer and Provider objects that describe our product and our service under test
  • Uses given to define the setup criteria for the Provider UserA exists and is not an administrator
  • Defines what the request that is expected to be made by the consumer will contain
  • Defines how the server is expected to respond

Using the Pact object as a context manager, we call our method under test which will then communicate with the Pact mock service. The mock service will respond with the items we defined, allowing us to assert that the method processed the response and returned the expected value. If you want more control over when the mock service is configured and the interactions verified, use the setup and verify methods, respectively:

   (pact
     .given('UserA exists and is not an administrator')
     .upon_receiving('a request for UserA')
     .with_request('get', '/users/UserA')
     .will_respond_with(200, body=expected))

    pact.setup()
    # Some additional steps before running the code under test
    result = user('UserA')
    # Some additional steps before verifying all interactions have occurred
    pact.verify()

Requests

When defining the expected HTTP request that your code is expected to make you can specify the method, path, body, headers, and query:

pact.with_request(
    method='GET',
    path='/api/v1/my-resources/',
    query={'search': 'example'}
)

query is used to specify URL query parameters, so the above example expects a request made to /api/v1/my-resources/?search=example.

pact.with_request(
    method='POST',
    path='/api/v1/my-resources/123',
    body={'user_ids': [1, 2, 3]},
    headers={'Content-Type': 'application/json'},
)

You can define exact values for your expected request like the examples above, or you can use the matchers defined later to assist in handling values that are variable.

The default hostname and port for the Pact mock service will be localhost:1234 but you can adjust this during Pact creation:

from pact import Consumer, Provider
pact = Consumer('Consumer').has_pact_with(
    Provider('Provider'), host_name='mockservice', port=8080)

This can be useful if you need to run to create more than one Pact for your test because your code interacts with two different services. It is important to note that the code you are testing with this contract must contact the mock service. So in this example, the user method could accept an argument to specify the location of the server, or retrieve it from an environment variable so you can change its URI during the test.

The mock service offers you several important features when building your contracts:

  • It provides a real HTTP server that your code can contact during the test and provides the responses you defined.
  • You provide it with the expectations for the request your code will make and it will assert the contents of the actual requests made based on your expectations.
  • If a request is made that does not match one you defined or if a request from your code is missing it will return an error with details.
  • Finally, it will record your contracts as a JSON file that you can store in your repository or publish to a Pact broker.

Expecting Variable Content

The above test works great if that user information is always static, but what happens if the user has a last updated field that is set to the current time every time the object is modified? To handle variable data and make your tests more robust, there are 3 helpful matchers:

Term(matcher, generate)

Asserts the value should match the given regular expression. You could use this to expect a timestamp with a particular format in the request or response where you know you need a particular format, but are unconcerned about the exact date:

from pact import Term
...
body = {
    'username': 'UserA',
    'last_modified': Term('\d+-\d+-\d+T\d+:\d+:\d+', '2016-12-15T20:16:01')
}

(pact
 .given('UserA exists and is not an administrator')
 .upon_receiving('a request for UserA')
 .with_request('get', '/users/UserA/info')
 .will_respond_with(200, body=body))

When you run the tests for the consumer, the mock service will return the value you provided as generate, in this case 2016-12-15T20:16:01. When the contract is verified on the provider, the regex will be used to search the response from the real provider service and the test will be considered successful if the regex finds a match in the response.

Like(matcher)

Asserts the element's type matches the matcher. For example:

from pact import Like
Like(123)  # Matches if the value is an integer
Like('hello world')  # Matches if the value is a string
Like(3.14)  # Matches if the value is a float

The argument supplied to Like will be what the mock service responds with.

When a dictionary is used as an argument for Like, all the child objects (and their child objects etc.) will be matched according to their types, unless you use a more specific matcher like a Term.

from pact import Like, Term
Like({
    'username': Term('[a-zA-Z]+', 'username'),
    'id': 123, # integer
    'confirmed': False, # boolean
    'address': { # dictionary
        'street': '200 Bourke St' # string
    }
})

EachLike(matcher, minimum=1)

Asserts the value is an array type that consists of elements like the one passed in. It can be used to assert simple arrays:

from pact import EachLike
EachLike(1)  # All items are integers
EachLike('hello')  # All items are strings

Or other matchers can be nested inside to assert more complex objects:

from pact import EachLike, Term
EachLike({
    'username': Term('[a-zA-Z]+', 'username'),
    'id': 123,
    'groups': EachLike('administrators')
})

Note, you do not need to specify everything that will be returned from the Provider in a JSON response, any extra data that is received will be ignored and the tests will still pass.

Note, to get the generated values from an object that can contain matchers like Term, Like, EachLike, etc. for assertion in self.assertEqual(result, expected) you may need to use get_generated_values() helper function:

from pact.matchers import get_generated_values
self.assertEqual(result, get_generated_values(expected))

Match common formats

Often times, you find yourself having to re-write regular expressions for common formats.

from pact import Format
Format().integer  # Matches if the value is an integer
Format().ip_address  # Matches if the value is an ip address

We've created a number of them for you to save you the time:

matcher description
identifier Match an ID (e.g. 42)
integer Match all numbers that are integers (both ints and longs)
decimal Match all real numbers (floating point and decimal)
hexadecimal Match all hexadecimal encoded strings
date Match string containing basic ISO8601 dates (e.g. 2016-01-01)
timestamp Match a string containing an RFC3339 formatted timestamp (e.g. Mon, 31 Oct 2016 15:21:41 -0400)
time Match string containing times in ISO date format (e.g. T22:44:30.652Z)
iso_datetime Match string containing ISO 8601 formatted dates (e.g. 2015-08-06T16:53:10+01:00)
iso_datetime_ms Match string containing ISO 8601 formatted dates, enforcing millisecond precision (e.g. 2015-08-06T16:53:10.123+01:00)
ip_address Match string containing IP4 formatted address
ipv6_address Match string containing IP6 formatted address
uuid Match strings containing UUIDs

These can be used to replace other matchers

from pact import Like, Format
Like({
    'id': Format().integer, # integer
    'lastUpdated': Format().timestamp, # timestamp
    'location': { # dictionary
        'host': Format().ip_address # ip address
    }
})

For more information see Matching

Uploading pact files to a Pact Broker

There are two ways to publish your pact files, to a Pact Broker.

  1. Pact CLI tools recommended
  2. Pact Python API

CLI

See Publishing and retrieving pacts

Example uploading to a Pact Broker

pact-broker publish /path/to/pacts/consumer-provider.json --consumer-app-version 1.0.0 --branch main --broker-base-url https://test.pactflow.io --broker-username someUsername --broker-password somePassword

Example uploading to a PactFlow Broker

pact-broker publish /path/to/pacts/consumer-provider.json --consumer-app-version 1.0.0 --branch main --broker-base-url https://test.pactflow.io --broker-token SomeToken

Python API

broker = Broker(broker_base_url="http://localhost")
broker.publish("TestConsumer",
                       "2.0.1",
                       branch='consumer-branch',
                       pact_dir='.')

output, logs = verifier.verify_pacts('./userserviceclient-userservice.json')

The parameters for this differ slightly in naming from their CLI equivalents:

CLI native Python
--branch branch
--build-url build_url
--auto-detect-version-properties auto_detect_version_properties
--tag=TAG consumer_tags
--tag-with-git-branch tag_with_git_branch
PACT_DIRS_OR_FILES pact_dir
--consumer-app-version version
n/a consumer_name

Verifying Pacts Against a Service

In addition to writing Pacts for Python consumers, you can also verify those Pacts against a provider of any language. There are two ways to do this.

CLI

After installing pact-python a pact-verifier application should be available. To get details about its use you can call it with the help argument:

pact-verifier --help

The simplest example is verifying a server with locally stored Pact files and no provider states:

pact-verifier --provider-base-url=http://localhost:8080 --pact-url=./pacts/consumer-provider.json

Which will immediately invoke the Pact verifier, making HTTP requests to the server located at http://localhost:8080 based on the Pacts in ./pacts/consumer-provider.json and reporting the results.

There are several options for configuring how the Pacts are verified:

--provider-base-url

Required. Defines the URL of the server to make requests to when verifying the Pacts.

--pact-url

Required if --pact-urls not specified. The location of a Pact file you want to verify. This can be a URL to a Pact Broker or a local path, to provide multiple files, specify multiple arguments.

pact-verifier --provider-base-url=http://localhost:8080 --pact-url=./pacts/one.json --pact-url=./pacts/two.json
--pact-urls

Required if --pact-url not specified. The location of the Pact files you want to verify. This can be a URL to a Pact Broker or one or more local paths, separated by a comma.

--provider-states-url

DEPRECATED AFTER v 0.6.0. The URL where your provider application will produce the list of available provider states. The verifier calls this URL to ensure the Pacts specify valid states before making the HTTP requests.

--provider-states-setup-url

The URL which should be called to setup a specific provider state before a Pact is verified. This URL will be called with a POST request, and the JSON body {consumer: 'Consumer name', state: 'a thing exists'}.

--pact-broker-url

Base URl for the Pact Broker instance to publish pacts to. Can also be specified via the environment variable PACT_BROKER_BASE_URL.

--pact-broker-username

The username to use when contacting the Pact Broker. Can also be specified via the environment variable PACT_BROKER_USERNAME.

--pact-broker-password

The password to use when contacting the Pact Broker. You can also specify this value as the environment variable PACT_BROKER_PASSWORD.

--pact-broker-token

The bearer token to use when contacting the Pact Broker. You can also specify this value as the environment variable PACT_BROKER_TOKEN.

--consumer-version-tag

Retrieve the latest pacts with this consumer version tag. Used in conjunction with --provider. May be specified multiple times.

--consumer-version-selector

You can also retrieve pacts with consumer version selector, a more flexible approach in specifying which pacts you need. May be specified multiple times. Read more about selectors here.

--provider-version-tag

Tag to apply to the provider application version. May be specified multiple times.

--provider-version-branch

Branch to apply to the provider application version.

--custom-provider-header

Header to add to provider state set up and pact verification requests e.g.Authorization: Basic cGFjdDpwYWN0 May be specified multiple times.

-t, --timeout

The duration in seconds we should wait to confirm that the verification process was successful. Defaults to 30.

-a, --provider-app-version

The provider application version. Required for publishing verification results.

-r, --publish-verification-results

Publish verification results to the broker.

Python API

You can use the Verifier class. This allows you to write native python code and the test framework of your choice.

verifier = Verifier(provider='UserService',
                    provider_base_url=PACT_URL)

# Using a local pact file

success, logs = verifier.verify_pacts('./userserviceclient-userservice.json')
assert success == 0

# Using a pact broker

- For OSS Pact Broker, use broker_username / broker_password
- For PactFlow Pact Broker, use broker_token

success, logs = verifier.verify_with_broker(
    # broker_username=PACT_BROKER_USERNAME,
    # broker_password=PACT_BROKER_PASSWORD,
    broker_url=PACT_BROKER_URL,
    broker_token=PACT_BROKER_TOKEN,
    publish_version=APPLICATION_VERSION,
    publish_verification_results=True,
    verbose=True,
    provider_version_branch=PROVIDER_BRANCH,
    enable_pending=True,
)
assert success == 0

The parameters for this differ slightly in naming from their CLI equivalents:

CLI native Python
--log-dir log_dir
--log-level log_level
--provider-app-version provider_app_version
--headers custom_provider_headers
--consumer-version-tag consumer_tags
--provider-version-tag provider_tags
--provider-states-setup-url provider_states_setup_url
--verbose verbose
--consumer-version-selector consumer_selectors
--publish-verification-results publish_verification_results
--provider-version-branch provider_version_branch

You can see more details in the examples

Provider States

In many cases, your contracts will need very specific data to exist on the provider to pass successfully. If you are fetching a user profile, that user needs to exist, if querying a list of records, one or more records needs to exist. To support decoupling the testing of the consumer and provider, Pact offers the idea of provider states to communicate from the consumer what data should exist on the provider.

When setting up the testing of a provider you will also need to setup the management of these provider states. The Pact verifier does this by making additional HTTP requests to the --provider-states-setup-url you provide. This URL could be on the provider application or a separate one. Some strategies for managing state include:

  • Having endpoints in your application that are not active in production that create and delete your datastore state
  • A separate application that has access to the same datastore to create and delete, like a separate App Engine module or Docker container pointing to the same datastore
  • A standalone application that can start and stop the other server with different datastore states

For more information about provider states, refer to the Pact documentation on Provider States.

Development

Please read CONTRIBUTING.md

To setup a development environment:

  1. If you want to run tests for all Python versions, install 2.7, 3.3, 3.4, 3.5, and 3.6 from source or using a tool like pyenv
  2. Its recommended to create a Python virtualenv for the project

To setup the environment, run tests, and package the application, run: make release

If you are just interested in packaging pact-python so you can install it using pip:

make package

This creates a dist/pact-python-N.N.N.tar.gz file, where the Ns are the current version. From there you can use pip to install it:

pip install ./dist/pact-python-N.N.N.tar.gz

Offline Installation of Standalone Packages

Although all Ruby standalone applications are predownloaded into the wheel artifact, it may be useful, for development, purposes to install custom Ruby binaries. In which case, use the bin-path flag.

pip install pact-python --bin-path=/absolute/path/to/folder/containing/pact/binaries/for/your/os

Pact binaries can be found at Pact Ruby Releases.

Testing

This project has unit and end to end tests, which can both be run from make:

Unit: make test

End to end: make e2e

Contact

Join us in slack: slack

or

More Repositories

1

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.
Ruby
2,160
star
2

pact-js

JS version of Pact. Pact is a contract testing framework for HTTP APIs and non-HTTP asynchronous messaging systems.
TypeScript
1,456
star
3

pact-jvm

JVM version of Pact. 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.
Kotlin
1,082
star
4

pact-go

Golang version of Pact. Pact is a contract testing framework for HTTP APIs and non-HTTP asynchronous messaging systems.
Go
857
star
5

pact-net

.NET version of Pact. 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.
C#
845
star
6

pact_broker

Enables your consumer driven contracts workflow
Ruby
702
star
7

pact-specification

Describes the pact format and verification specifications
295
star
8

pact-php

PHP version of Pact. 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
PHP
244
star
9

pact.io

Pact Foundation Website
HTML
219
star
10

pact-js-core

Core binaries for pact-js, a Contract Testing Framework. NOTE: If you are looking to do Pact contract testing in node, you almost certainly want pact-js, not pact-node.
TypeScript
146
star
11

pact-workshop-js

Pact JS workshop - learn Pact in 60 minutes
JavaScript
129
star
12

pact-workshop-go

Golang Pact workshop
Go
106
star
13

pact-broker-docker

Dockerized Pact Broker
Shell
92
star
14

pact-reference

Reference implementations for the pact specifications
Rust
87
star
15

pact-workshop-jvm-spring

Example Spring Boot project for the Pact workshop
Java
85
star
16

jest-pact

A Pact adaptor for to allow you to easily run tests with Jest
TypeScript
81
star
17

pact-workshop-dotnet-core-v1

A workshop for Pact using .NET Core
C#
76
star
18

pact-stub-server

Standalone pact stub server
Rust
75
star
19

pact-mock_service

Provides a mock service for use with Pact
Ruby
73
star
20

pact_broker-client

A Ruby and CLI client for the Pact Broker. Publish and retrieve pacts and verification results.
Ruby
69
star
21

nestjs-pact

Injectable Pact.js Consumer/Producer for NestJS
TypeScript
48
star
22

devrel

Developer Relations @ Pact - Your map to the Pact landscape for all-comers (maintainers, contributors, users, newbies)
38
star
23

pact-ruby-standalone

A standalone pact command line executable using the ruby pact implementation and Travelling Ruby
Shell
35
star
24

pact-provider-verifier

Cross-platform, generic language, Pact provider verification tool
Ruby
28
star
25

pact-workshop-Maven-Springboot-JUnit5

Pact Maven + Springboot + JUnit5 workshop - learn Pact in 60 minutes
28
star
26

pact-stub-server-archived

Wraps the Pact Rust mock server in a Docker container
Dockerfile
25
star
27

pact-js-mocha

EXPERIMENTAL Mocha Interface for Pact JS (warning: it is not recommended for beginners)
JavaScript
18
star
28

pact-plugins

🏰 Architecture to support Plugins πŸ”Œ with Pact πŸ”—
Rust
16
star
29

pact-provider-proxy

Allows pact verification against a running provider at a configurable base URL
Ruby
16
star
30

docs.pact.io

Pact documentation website
HTML
15
star
31

pact-broker-chart

This repository houses the Pact Broker Helm Chart
Smarty
11
star
32

karma-pact

Pact Framework Plugin for Karma
Shell
11
star
33

pact-5-minute-getting-started-guide

JavaScript
11
star
34

pact-ruby-cli

Amalgamated Pact Ruby CLI
Shell
11
star
35

pact-support

Shared code for Pact gems
Ruby
7
star
36

pact_broker-serverless

Pact Broker running in AWS Lambda with Serverless
Shell
7
star
37

pact-cplusplus

C++ DSL for Pact Library
HTML
6
star
38

pact-ruby-e2e-example

Code base to use for demonstrating features and recreating issues in the ruby implementation of pact. Please fork it and modify to recreate your own code.
Ruby
5
star
39

pact-xml

XML support for the Pact gem
Ruby
4
star
40

pact-plugin-template-golang

Pact πŸ”— Plugin πŸ”Œ template for the GoLang 🐿️ language = 🫢
Go
4
star
41

pact-message-demo

Ruby
4
star
42

pact-consumer-minitest

Minitest support for the Pact Consumer gem
Ruby
4
star
43

serverless-offline-pact

A serverless offline plugin to start one or more pact stub service alongside your serverless application
TypeScript
3
star
44

pact-mock-service-docker

Docker image running the Pact mock service
Ruby
3
star
45

homebrew-pact-ruby-standalone

The Pact Ruby Standalone public homebrew tap for macos/linux homebrew formulae
Shell
3
star
46

.github

The GitHub landing page for Pact - The de-facto contract testing tool
3
star
47

pact-event-bot

TypeScript
2
star
48

pact-standalone-npm

Pact Standalone wrapper for NPM projects
JavaScript
2
star
49

pact-parser

Small server to aid using existing pacts files as data sources in unit and integrational testing.
JavaScript
2
star
50

pact-mock-service-npm

Shell
2
star
51

pact-js-cli

The Broker CLI for Pact, but available to your node scripts
TypeScript
2
star
52

grunt-pact

A grunt task to run pact-node
JavaScript
1
star
53

pactr

R version of Pact. Enables consumer driven contract testing. Please read the Pact.io for specific information about PACT.
R
1
star
54

release-gem

Github action that bumps the version, generates the changelog, releases the gem, and creates a Github release
Shell
1
star
55

mocha-pact

An adaptor to allow you to easily run pact tests with Mocha
TypeScript
1
star
56

pact-ruby-standalone-e2e-example

Code base to use for demonstrating features and recreating issues in the Ruby standalone implementation of pact. Please fork it and modify to recreate your own code.
Shell
1
star
57

cypress-pact

Pact plugin for integrating Pact with Cypress tests
1
star
58

pact-js-dev-config

Shared configs for developers working on pact-js and related projects
JavaScript
1
star
59

blog.pact.io

Ghost application setup for blog.pact.io
SCSS
1
star