Skip to content

Commit aa0f07e

Browse files
committed
chore(example): update readme
Update the README for the examples to match the new structure of the examples. Signed-off-by: JP-Ellis <josh@jpellis.me>
1 parent 9488f0e commit aa0f07e

File tree

1 file changed

+143
-201
lines changed

1 file changed

+143
-201
lines changed

examples/README.md

Lines changed: 143 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,220 +1,162 @@
11
# Examples
22

3-
## Table of Contents
3+
This directory contains an end-to-end example of using Pact in Python. While
4+
this document and the documentation within the examples themselves are intended
5+
to be mostly self-contained, it is highly recommended that you read the [Pact
6+
Documentation](https://docs.pact.io/) as well.
47

5-
* [Overview](#overview)
6-
* [broker](#broker)
7-
* [common](#common)
8-
* [consumer](#consumer)
9-
* [flask_provider](#flask_provider)
10-
* [fastapi_provider](#fastapi_provider)
11-
* [message](#message)
12-
* [pacts](#pacts)
8+
Assuming you have [hatch](https://hatch.pypa.io/latest/) installed, the example
9+
suite can be executed with:
1310

14-
## Overview
15-
16-
Here you can find examples of how to use Pact using the python language. You can find more of an overview on Pact in the
17-
[Pact Introduction].
18-
19-
Examples are given of both the [Consumer] and [Provider], this does not mean however that you must use python for both.
20-
Different languages can be mixed and matched as required.
21-
22-
In these examples, `1` is just used to meet the need of having *some* [Consumer] or [Provider] version. In reality, you
23-
will generally want to use something more complicated and automated. Guidelines and best practices are available in the
24-
[Versioning in the Pact Broker]
25-
26-
## broker
27-
28-
The [Pact Broker] stores [Pact file]s and [Pact verification] results. It is used here for the [consumer](#consumer),
29-
[flask_provider](#flask-provider) and [message](#message) tests.
30-
31-
### Running
32-
33-
These examples run the [Pact Broker] as part of the tests when specified. It can be run outside the tests as well by
34-
performing the following command from a separate terminal in the `examples/broker` folder:
35-
```bash
36-
docker-compose up
11+
```sh
12+
hatch run example
3713
```
3814

39-
You should then be able to open a browser and navigate to http://localhost where you will initially be able to see the
40-
default Example App/Example API Pact.
41-
42-
Running the [Pact Broker] outside the tests will mean you are able to then see the [Pact file]s submitted to the
43-
[Pact Broker] as the various tests are performed.
44-
45-
## common
46-
47-
To avoid needing to duplicate certain fixtures, such as starting up a docker based Pact broker (to demonstrate how the
48-
test process could work), the shared fixtures used by the pytests have all been placed into a single location.]
49-
This means it is easier to see the relevant code for the example without having to go through the boilerplate fixtures.
50-
See [Requiring/Loading plugins in a test module or conftest file] for further details of this approach.
51-
52-
## consumer
53-
54-
Pact is consumer-driven, which means first the contracts are created. These Pact contracts are generated during
55-
execution of the consumer tests.
56-
57-
### Running
58-
59-
When the tests are run, the "minimum" is to generate the Pact contract JSON, additional options are available. The
60-
following commands can be run from the `examples/consumer` folder:
61-
62-
- Install any necessary dependencies:
63-
```bash
64-
pip install -r requirements.txt
65-
```
66-
- To startup the broker, run the tests, and publish the results to the broker:
67-
```bash
68-
pytest --run-broker True --publish-pact 1
69-
```
70-
- Alternatively the same can be performed with the following command, which is called from a `make consumer`:
71-
```bash
72-
./run_pytest.sh
73-
```
74-
- To run the tests, and publish the results to the broker which is already running:
75-
```bash
76-
pytest --publish-pact 1
77-
```
78-
- To just run the tests:
79-
```bash
80-
pytest
81-
```
82-
83-
### Output
84-
85-
The following file(s) will be created when the tests are run:
86-
87-
| Filename | Contents |
88-
|---------------------------------------------| ----------|
89-
| consumer/pact-mock-service.log | All interactions with the mock provider such as expected interactions, requests, and interaction verifications. |
90-
| consumer/userserviceclient-userservice.json | This contains the Pact interactions between the `UserServiceClient` and `UserService`, as defined in the tests. The naming being derived from the named Pacticipants: `Consumer("UserServiceClient")` and `Provider("UserService")` |
91-
92-
## flask_provider
93-
94-
The Flask [Provider] example consists of a basic Flask app, with a single endpoint route.
95-
This implements the service expected by the [consumer](#consumer).
96-
97-
Functionally, this provides the same service and tests as the [fastapi_provider](#fastapi_provider). Both are included to
98-
demonstrate how Pact can be used in different environments with different technology stacks and approaches.
99-
100-
The [Provider] side is responsible for performing the tests to verify if it is compliant with the [Pact file] contracts
101-
associated with it.
102-
103-
As such, the tests use the pact-python Verifier to perform this verification. Two approaches are demonstrated:
104-
- Testing against the [Pact broker]. Generally this is the preferred approach, see information on [Sharing Pacts].
105-
- Testing against the [Pact file] directly. If no [Pact broker] is available you can verify against a static [Pact file].
15+
The code within the examples is intended to be well documented and you are
16+
encouraged to look through the code as well (or submit a PR if anything is
17+
unclear!).
10618

107-
### Running
19+
## Overview
10820

109-
To avoid package version conflicts with different applications, it is recommended to run these tests from a
110-
[Virtual Environment]
21+
Pact is a contract testing tool. Contract testing is a way to ensure that
22+
services (such as an API provider and a client) can communicate with each other.
23+
This example focuses on HTTP interactions, but Pact can be used to test more
24+
general interactions as well such as through message queues.
11125

112-
The following commands can be run from within your [Virtual Environment], in the `examples/flask_provider`.
26+
An interaction between a HTTP client (the _consumer_) and a server (the
27+
_provider_) would typically look like this:
11328

114-
To perform the python tests:
115-
```bash
116-
pip install -r requirements.txt # Install the dependencies for the Flask example
117-
pip install -e ../../ # Using setup.py in the pact-python root, install any pact dependencies and pact-python
118-
./run_pytest.sh # Wrapper script to first run Flask, and then run the tests
119-
```
29+
<div align="center">
12030

121-
To perform verification using CLI to verify the [Pact file] against the Flask [Provider] instead of the python tests:
122-
```bash
123-
pip install -r requirements.txt # Install the dependencies for the Flask example
124-
./verify_pact.sh # Wrapper script to first run Flask, and then use `pact-verifier` to verify locally
31+
```mermaid
32+
sequenceDiagram
33+
participant Consumer
34+
participant Provider
35+
Consumer ->> Provider: GET /users/123
36+
Provider ->> Consumer: 200 OK
37+
Consumer ->> Provider: GET /users/999
38+
Provider ->> Consumer: 404 Not Found
12539
```
12640

127-
To perform verification using CLI, but verifying the [Pact file] previously provided by a [Consumer], and publish the
128-
results. This example requires that the [Pact broker] is already running, and the [Consumer] tests have been published
129-
already, described in the [consumer](#consumer) section above.
130-
```bash
131-
pip install -r requirements.txt # Install the dependencies for the Flask example
132-
./verify_pact.sh 1 # Wrapper script to first run Flask, and then use `pact-verifier` to verify and publish
41+
</div>
42+
43+
To test this interaction naively would require both the consumer and provider to
44+
be running at the same time. While this is straightforward in the above example,
45+
this quickly becomes impractical as the number of interactions grows between
46+
many microservices. Pact solves this by allowing the consumer and provider to be
47+
tested independently.
48+
49+
Pact achieves this be mocking the other side of the interaction:
50+
51+
<div align="center">
52+
53+
```mermaid
54+
sequenceDiagram
55+
box Consumer Side
56+
participant Consumer
57+
participant P1 as Pact
58+
end
59+
box Provider Side
60+
participant P2 as Pact
61+
participant Provider
62+
end
63+
Consumer->>P1: GET /users/123
64+
P1->>Consumer: 200 OK
65+
Consumer->>P1: GET /users/999
66+
P1->>Consumer: 404 Not Found
67+
68+
P1--)P2: Pact Broker
69+
70+
P2->>Provider: GET /users/123
71+
Provider->>P2: 200 OK
72+
P2->>Provider: GET /users/999
73+
Provider->>P2: 404 Not Found
13374
```
13475

135-
These examples demonstrate by first launching Flask via a `python -m flask run`, you may prefer to start Flask using an
136-
`app.run()` call in the python code instead, see [How to Run a Flask Application]. Additionally for tests, you may want
137-
to manage starting and stopping Flask as part of a fixture setup. Any approach can be chosen here, in line with your
138-
existing Flask testing practices.
139-
140-
### Output
141-
142-
The following file(s) will be created when the tests are run
143-
144-
| Filename | Contents |
145-
|-----------------------------| ----------|
146-
| flask_provider/log/pact.log | All Pact interactions with the Flask Provider. Every interaction example retrieved from the Pact Broker will be performed during the Verification test; the request/response logged here. |
147-
148-
## fastapi_provider
149-
150-
The FastAPI [Provider] example consists of a basic FastAPI app, with a single endpoint route.
151-
This implements the service expected by the [consumer](#consumer).
152-
153-
Functionally, this provides the same service and tests as the [flask_provider](#flask_provider). Both are included to
154-
demonstrate how Pact can be used in different environments with different technology stacks and approaches.
155-
156-
The [Provider] side is responsible for performing the tests to verify if it is compliant with the [Pact file] contracts
157-
associated with it.
158-
159-
As such, the tests use the pact-python Verifier to perform this verification. Two approaches are demonstrated:
160-
- Testing against the [Pact broker]. Generally this is the preferred approach, see information on [Sharing Pacts].
161-
- Testing against the [Pact file] directly. If no [Pact broker] is available you can verify against a static [Pact file].
162-
-
163-
### Running
164-
165-
To avoid package version conflicts with different applications, it is recommended to run these tests from a
166-
[Virtual Environment]
167-
168-
The following commands can be run from within your [Virtual Environment], in the `examples/fastapi_provider`.
169-
170-
To perform the python tests:
171-
```bash
172-
pip install -r requirements.txt # Install the dependencies for the FastAPI example
173-
pip install -e ../../ # Using setup.py in the pact-python root, install any pact dependencies and pact-python
174-
./run_pytest.sh # Wrapper script to first run FastAPI, and then run the tests
175-
```
176-
177-
To perform verification using CLI to verify the [Pact file] against the FastAPI [Provider] instead of the python tests:
178-
```bash
179-
pip install -r requirements.txt # Install the dependencies for the FastAPI example
180-
./verify_pact.sh # Wrapper script to first run FastAPI, and then use `pact-verifier` to verify locally
76+
</div>
77+
78+
In the first stage, the consumer defines a number of interactions in the form
79+
below. Pact sets up a mock server that will respond to the requests as defined
80+
by the consumer. All these interactions, containing both the request and
81+
expected response, are all sent to the Pact Broker.
82+
83+
> Given {provider state} \
84+
> Upon receiving {description} \
85+
> With {request} \
86+
> Will respond with {response}
87+
88+
In the second stage, the provider retrieves the interactions from the Pact
89+
Broker. It then sets up a mock client that will make the requests as defined by
90+
the consumer. Pact then verifies that the responses from the provider match the
91+
expected responses defined by the consumer.
92+
93+
In this way, Pact is consumer driven and can ensure that the provider is
94+
compatible with the consumer. While this example showcases both sides in Python,
95+
this is absolutely not required. The provider could be written in any language,
96+
and satisfy contracts from a number of consumers all written in different
97+
languages.
98+
99+
### Consumer
100+
101+
The consumer in this example is a simple Python script that makes a HTTP GET
102+
request to a server. It is defined in [`src/consumer.py`](src/consumer.py). The
103+
tests for the consumer are defined in
104+
[`tests/test_00_consumer.py`](tests/test_00_consumer.py). Each interaction is
105+
defined using the format mentioned above. Programmatically, this looks like:
106+
107+
```py
108+
expected: dict[str, Any] = {
109+
"id": Format().integer,
110+
"name": "Verna Hampton",
111+
"created_on": Format().iso_8601_datetime(),
112+
}
113+
(
114+
pact.given("user 123 exists")
115+
.upon_receiving("a request for user 123")
116+
.with_request("get", "/users/123")
117+
.will_respond_with(200, body=Like(expected))
118+
)
119+
# Code that makes the request to the server
181120
```
182121

183-
To perform verification using CLI, but verifying the [Pact file] previously provided by a [Consumer], and publish the
184-
results. This example requires that the [Pact broker] is already running, and the [Consumer] tests have been published
185-
already, described in the [consumer](#consumer) section above.
186-
```bash
187-
pip install -r requirements.txt # Install the dependencies for the FastAPI example
188-
./verify_pact.sh 1 # Wrapper script to first run FastAPI, and then use `pact-verifier` to verify and publish
122+
### Provider
123+
124+
This example showcases to different providers, one written in Flask and one
125+
written in FastAPI. Both are simple Python web servers that respond to a HTTP
126+
GET request. The Flask provider is defined in [`src/flask.py`](src/flask.py) and
127+
the FastAPI provider is defined in [`src/fastapi.py`](src/fastapi.py). The
128+
tests for the providers are defined in
129+
[`tests/test_01_provider_flask.py`](tests/test_01_provider_flask.py) and
130+
[`tests/test_01_provider_fastapi.py`](tests/test_01_provider_fastapi.py).
131+
132+
Unlike the consumer side, the provider side is responsible to responding to the
133+
interactions defined by the consumers. In this regard, the provider testing
134+
is rather simple:
135+
136+
```py
137+
code, _ = verifier.verify_with_broker(
138+
broker_url=str(broker),
139+
published_verification_results=True,
140+
provider_states_setup_url=str(PROVIDER_URL / "_pact" / "provider_states"),
141+
)
142+
assert code == 0
189143
```
190144

191-
### Output
192-
193-
The following file(s) will be created when the tests are run
194-
195-
| Filename | Contents |
196-
|-------------------------------| ----------|
197-
| fastapi_provider/log/pact.log | All Pact interactions with the FastAPI Provider. Every interaction example retrieved from the Pact Broker will be performed during the Verification test; the request/response logged here. |
198-
199-
200-
## message
201-
202-
TODO
203-
204-
## pacts
205-
206-
Both the Flask and the FastAPI [Provider] examples implement the same service the [Consumer] example interacts with.
207-
This folder contains the generated [Pact file] for reference, which is also used when running the [Provider] tests
208-
without a [Pact Broker].
209-
210-
[Pact Broker]: https://docs.pact.io/pact_broker
211-
[Pact Introduction]: https://docs.pact.io/
212-
[Consumer]: https://docs.pact.io/getting_started/terminology#service-consumer
213-
[Provider]: https://docs.pact.io/getting_started/terminology#service-provider
214-
[Versioning in the Pact Broker]: https://docs.pact.io/getting_started/versioning_in_the_pact_broker/
215-
[Pact file]: https://docs.pact.io/getting_started/terminology#pact-file
216-
[Pact verification]: https://docs.pact.io/getting_started/terminology#pact-verification]
217-
[Virtual Environment]: https://docs.python.org/3/tutorial/venv.html
218-
[Sharing Pacts]: https://docs.pact.io/getting_started/sharing_pacts/]
219-
[How to Run a Flask Application]: https://www.twilio.com/blog/how-run-flask-application
220-
[Requiring/Loading plugins in a test module or conftest file]: https://docs.pytest.org/en/6.2.x/writing_plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file
145+
The complication comes from the fact that the provider needs to know what state
146+
to be in before responding to the request. In order to achieve this, a testing
147+
endpoint is defined that sets the state of the provider as defined in the
148+
`provider_states_setup_url` above. For example, the consumer requests has _Given
149+
user 123 exists_ as the provider state, and the provider will need to ensure
150+
that this state is satisfied. This would typically entail setting up a database
151+
with the correct data, but it is advisable to achieve the equivalent state by
152+
mocking the appropriate calls. This has been showcased in both provider
153+
examples.
154+
155+
### Broker
156+
157+
The broker acts as the intermediary between these test suites. It stores the
158+
interactions defined by the consumer and makes them available to the provider.
159+
Once the provider has verified that it satisfies all interactions, the broker
160+
also stores the verification results. The example here runs the open source
161+
broker within a Docker container. An alternative is to use the hosted [Pactflow
162+
service](https://pactflow.io).

0 commit comments

Comments
 (0)