|
1 | 1 | # Examples |
2 | 2 |
|
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. |
4 | 7 |
|
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: |
13 | 10 |
|
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 |
37 | 13 | ``` |
38 | 14 |
|
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!). |
106 | 18 |
|
107 | | -### Running |
| 19 | +## Overview |
108 | 20 |
|
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. |
111 | 25 |
|
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: |
113 | 28 |
|
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"> |
120 | 30 |
|
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 |
125 | 39 | ``` |
126 | 40 |
|
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 |
133 | 74 | ``` |
134 | 75 |
|
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 |
181 | 120 | ``` |
182 | 121 |
|
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 |
189 | 143 | ``` |
190 | 144 |
|
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