Skip to content

A brief lesson on pact.io using pact-python, pact broker, and simple examples of a client, server, and contract tests.

License

Notifications You must be signed in to change notification settings

svulpius-ns/pact-python-demo

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pact-python demo

This simple client/server implementation demos how pact-python can be used for a contract test.

Further reading about pact is at https://docs.pact.io/ or https://docs.pact.io/blogs-videos-and-articles

It has 3 components:

  • pact_python_demo/user-app.py -- a simple flask app that has a REST endpoint for /users/<name> which returns a JSON representation of a user
  • pact_python_demo/client.py -- a simple client that gets a user from user-app.
  • tests/test_client.py -- a set of test cases using pytest and pact-python to test a simple contract between the client and server.
  • broker/ -- contains docker-compose files for a pact broker server

Set up your virtual environment with:

$ pip install pipenv
$ pipenv install

Creating/validating pacts without the broker

Enter your venv shell with:

$ pipenv shell

Run the test with:

(venv) $ pytest

You'll see a pact file is generated by this consumer test at tests/userserviceclient-userservice.json

Then, fire up your server-side app and verify the provider works as expected:

(venv) $ python pact_python_demo/user-app.py
(venv) $ pact-verifier --provider-base-url=http://localhost:5001 \
            --pact-url=tests/userserviceclient-userservice.json \
            --provider-states-setup-url=http://localhost:5001/_pact/provider_states

Creating/validating pacts and publishing results to the pact broker

Next, you can try incorporating the pact broker in this process:

Start the broker server:

$ cd broker
$ docker-compose up

It's accessible at http://127.0.0.1 with username 'pactbroker' and password 'pactbroker'

To run the test, this time pushing the generated pact into the broker with version '0.1' of UserServiceClient, use:

(venv) $ pytest --publish-pact 0.1

The tests/conftest.py adds this custom option, and tests/test_client has code in the pact fixture to upload to the broker if the command line option was passed.

Then, you can validate the provider (UserService) and push the result to the broker by running:

(venv) $ python pact_python_demo/user-app.py
(venv) ./verify_pact.sh 0.2

This runs the same pact-verifier command as above but has additional args for pulling the pact file from the broker (instead of getting the .json locally) and pushing the verification result to the broker.

Log in to the broker and take a look at the matrix. You will see that UserClientService version 0.1 has been verified against UserService version 0.2

How does this work?

The purpose of test_client.py is to simply verify that if the server sends me what I'm expecting, MY client code behaves properly. It is essentially a unit test to exercise your client code using a mocked server. Except in this case, the mock server is now a "pact mock provider". In the test, you have configured the mock provider server to respond to your client with certain data. This is the mock data to represent how you'd expect the provider to behave given the proper state exists on the provider. Using that mock data, you verify your client-side code works.

Two tests were created to verify that my client behaves properly if: a) I get a 200OK response back w/ the json I'd expect to see, or b) I get a 404 back and I should be returning None

For example, client expects that if UserA exists and it sends a GET to /users/UserA, it will get back JSON about UserA that has a name, uuid, timestamp for when the user was created, and a boolean indicating if the users is an admin or not. If the server responds with a 404, user client should return None.

When your tests pass, a json pact file is generated. The user-app team can then take this pact file, and use it to verify that their service responds to requests in the way your client expects.

Provider states

A key part of this is provider states:

  • client.py expects a 200OK response with json about 'UserA' given that userA exists on the provider
  • In order for pact to verify the provider, user-app.py has to implement ways of setting up the needed state. This is done via a provider-setup-url which may reside on the provider app itself, or it could reside somewhere else (for example, some other test service you have setup that lives outside your provider app to populate your backend DB)
  • In this demo, the user app has provided another REST endpoint at /_pact/provider_states which the pact-verifier will send POST requests to. Depending on the state the pact-verifier asks for, the provider will need to set itself up (e.g. make DB changes, or do other things) to get itself into "the right state" before the verifier sends the "request under test" to it.

Isn't my server gonna send back different stuff?

Yes. The real server may respond with a timestamp or uuid that does not necessarily look like the mock values you created in your tests. Therefore, the Term object is used to say that the server could send me anything that looks like this regex and that would be good for me, but for the purpose of this test I am using <this fake value>.

The generated pact file used to verify the provider will only say "you need to be sending me data that looks like this regex" for timestamp and id. But, my client is not going to let server return with name=UserB if it asked for name=UserA, so the pact file is essentially saying "given that User A exists, and I send you a GET for userA, name in the JSON should be UserA"

About

A brief lesson on pact.io using pact-python, pact broker, and simple examples of a client, server, and contract tests.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 90.1%
  • Shell 9.9%