Skip to content

Commit 140f583

Browse files
Merge pull request #8 from pact-foundation/manage-mock-service
pact-python manages the mock service
2 parents fd68b41 + 5994c3a commit 140f583

File tree

14 files changed

+437
-107
lines changed

14 files changed

+437
-107
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
v0.1.0, 2017-03-30 -- Basic support for authoring contracts against a separately running mock service
2+
v0.2.0, 2017-04-22 -- Pact Python manages the Pact mock service

Makefile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ e2e:
3939
sh -c '\
4040
cd e2e; \
4141
docker-compose pull > /dev/null; \
42-
docker-compose up -d pactmockservice; \
43-
while ! nc -z localhost 1234; do sleep 0.1; done; \
44-
docker-compose logs --follow > ./pacts/mock-service-logs.txt & \
4542
nosetests ./contracts; \
4643
docker-compose down; \
4744
docker-compose up -d app pactverifier; \

README.md

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,19 @@ def user(user_name):
4040
Then `Consumer`'s contract test might look something like this:
4141

4242
```python
43+
import atexit
4344
import unittest
4445

4546
from pact import Consumer, Provider
4647

4748

49+
pact = Consumer('Consumer').has_pact_with(Provider('Provider'))
50+
pact.start_service()
51+
atexit.register(pact.stop_service)
52+
53+
4854
class GetUserInfoContract(unittest.TestCase):
4955
def test_get_user(self):
50-
pact = Consumer('Consumer').has_pact_with(Provider('Provider'))
5156
expected = {
5257
'username': 'UserA',
5358
'id': 123,
@@ -77,7 +82,22 @@ This does a few important things:
7782
Using the Pact object as a [context manager], we call our method under test
7883
which will then communicate with the Pact mock service. The mock service will respond with
7984
the items we defined, allowing us to assert that the method processed the response and
80-
returned the expected value.
85+
returned the expected value. If you want more control over when the mock service is
86+
configured and the interactions verified, use the `setup` and `verify` methods, respectively:
87+
88+
```python
89+
(pact
90+
.given('UserA exists and is not an administrator')
91+
.upon_receiving('a request for UserA')
92+
.with_request('get', '/users/UserA')
93+
.will_respond_with(200, body=expected))
94+
95+
pact.setup()
96+
# Some additional steps before running the code under test
97+
result = user('UserA')
98+
# Some additional steps before verifying all interactions have occurred
99+
pact.verify()
100+
````
81101

82102
The default hostname and port for the Pact mock service will be
83103
`localhost:1234` but you can adjust this during Pact creation:
@@ -88,13 +108,12 @@ pact = Consumer('Consumer').has_pact_with(
88108
Provider('Provider'), host_name='mockservice', port=8080)
89109
```
90110

91-
This can be useful if you are running your tests and the mock service inside a Docker
92-
network, where you want to reference the service by its Docker name, instead of via
93-
the `localhost` interface. It is important to note that the code you are testing with
94-
this contract _must_ contact the mock service. So in this example, the `user` method
95-
could accept an argument to specify the location of the server, or retrieve it from an
96-
environment variable so you can change its URI during the test. Another option is to
97-
specify a Docker network alias so the requests that you make will go to the container.
111+
This can be useful if you need to run to create more than one Pact for your test
112+
because your code interacts with two different services. It is important to note
113+
that the code you are testing with this contract _must_ contact the mock service.
114+
So in this example, the `user` method could accept an argument to specify the
115+
location of the server, or retrieve it from an environment variable so you can
116+
change its URI during the test.
98117

99118
The mock service offers you several important features when building your contracts:
100119
- It provides a real HTTP server that your code can contact during the test and provides the responses you defined.

e2e/__init__.py

Whitespace-only changes.

e2e/contracts/test_e2e.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import atexit
2+
13
import requests
24
import unittest
35

@@ -6,29 +8,32 @@
68
from pact.provider import Provider
79

810

11+
pact = Consumer('consumer').has_pact_with(Provider('provider'))
12+
pact.start_service()
13+
atexit.register(pact.stop_service)
14+
15+
916
class BaseTestCase(unittest.TestCase):
10-
def setUp(self):
11-
self.pact = Consumer('consumer').has_pact_with(
12-
Provider('provider'))
17+
pass
1318

1419

1520
class ExactMatches(BaseTestCase):
1621
def test_get_user_sparse(self):
1722
expected = {'name': 'Jonas'}
18-
(self.pact
23+
(pact
1924
.given('a simple json blob exists')
2025
.upon_receiving('a request for a user')
2126
.with_request('get', '/users/Jonas')
2227
.will_respond_with(200, body=expected))
2328

24-
with self.pact:
29+
with pact:
2530
result = requests.get('http://localhost:1234/users/Jonas')
2631

2732
self.assertEqual(result.json(), expected)
2833

2934
def test_post_user_complex(self):
3035
expected = {'name': 'Jonas'}
31-
(self.pact
36+
(pact
3237
.given('a simple json blob exists')
3338
.upon_receiving('a query for the user Jonas')
3439
.with_request(
@@ -42,7 +47,7 @@ def test_post_user_complex(self):
4247
body=expected,
4348
headers={'Content-Type': 'application/json'}))
4449

45-
with self.pact:
50+
with pact:
4651
result = requests.post(
4752
'http://localhost:1234/users/?Jonas',
4853
headers={'Accept': 'application/json'},
@@ -53,21 +58,21 @@ def test_post_user_complex(self):
5358

5459
class InexactMatches(BaseTestCase):
5560
def test_sparse(self):
56-
(self.pact
61+
(pact
5762
.given('the user `bob` exists')
5863
.upon_receiving('a request for the user object of `bob`')
5964
.with_request('get', '/users/bob')
6065
.will_respond_with(200, body={
6166
'username': SomethingLike('bob'),
6267
'id': Term('\d+', '123')}))
6368

64-
with self.pact:
69+
with pact:
6570
result = requests.get('http://localhost:1234/users/bob')
6671

6772
self.assertEqual(result.json(), {'username': 'bob', 'id': '123'})
6873

6974
def test_nested(self):
70-
(self.pact
75+
(pact
7176
.given('a list of users exists')
7277
.upon_receiving('a query of all users')
7378
.with_request('get', '/users/', query={'limit': Term('\d+', '5')})
@@ -77,7 +82,7 @@ def test_nested(self):
7782
'groups': EachLike(123),
7883
}, minimum=2)}))
7984

80-
with self.pact:
85+
with pact:
8186
results = requests.get(
8287
'http://localhost:1234/users/?limit=4')
8388

@@ -89,7 +94,7 @@ def test_nested(self):
8994

9095
class SyntaxErrors(BaseTestCase):
9196
def test_incorrect_number_of_arguments(self):
92-
(self.pact
97+
(pact
9398
.given('a list of users exists')
9499
.upon_receiving('a request for a user')
95100
.with_request('get', '/users/')
@@ -99,7 +104,7 @@ def two(a, b):
99104
print('Requires two arguments')
100105

101106
with self.assertRaises(TypeError) as e:
102-
with self.pact:
107+
with pact:
103108
two('one')
104109

105110
self.assertEqual(

pact/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77

88
__all__ = ('Consumer', 'EachLike', 'Pact', 'Provider', 'SomethingLike', 'Term')
9-
__version__ = '0.1.0'
9+
__version__ = '0.2.0'

pact/constants.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Constant values for the pact-python package."""
2+
import os
3+
from os.path import join, dirname, normpath
4+
5+
6+
def mock_service_exe():
7+
"""Get the appropriate executable name for this platform."""
8+
if os.name == 'nt':
9+
return 'pact-mock-service.bat'
10+
else:
11+
return 'pact-mock-service'
12+
13+
14+
MOCK_SERVICE_PATH = normpath(join(
15+
dirname(__file__), 'bin', 'mock-service', 'bin', mock_service_exe()))

pact/consumer.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ def __init__(self, name, service_cls=Pact):
3131
self.name = name
3232
self.service_cls = service_cls
3333

34-
def has_pact_with(self, provider, host_name='localhost', port=1234):
34+
def has_pact_with(self, provider, host_name='localhost', port=1234,
35+
log_dir=None, ssl=False, sslcert=None, sslkey=None,
36+
cors=False, pact_dir=None, version='2.0.0'):
3537
"""
3638
Create a contract between the `provider` and this consumer.
3739
@@ -56,6 +58,28 @@ def has_pact_with(self, provider, host_name='localhost', port=1234):
5658
This will need to tbe the same port used by your code under test
5759
to contact the mock service. It defaults to: 1234
5860
:type port: int
61+
:param log_dir: The directory where logs should be written. Defaults to
62+
the current directory.
63+
:type log_dir: str
64+
:param ssl: Flag to control the use of a self-signed SSL cert to run
65+
the server over HTTPS , defaults to False.
66+
:type ssl: bool
67+
:param sslcert: Path to a custom self-signed SSL cert file, 'ssl'
68+
option must be set to True to use this option. Defaults to None.
69+
:type sslcert: str
70+
:param sslkey: Path to a custom key and self-signed SSL cert key file,
71+
'ssl' option must be set to True to use this option.
72+
Defaults to None.
73+
:type sslkey: str
74+
:param cors: Allow CORS OPTION requests to be accepted,
75+
defaults to False.
76+
:type cors: bool
77+
:param pact_dir: Directory where the resulting pact files will be
78+
written. Defaults to the current directory.
79+
:type pact_dir: str
80+
:param version: The Pact Specification version to use, defaults to
81+
'2.0.0'.
82+
:type version: str
5983
:return: A Pact object which you can use to define the specific
6084
interactions your code will have with the provider.
6185
:rtype: pact.Pact
@@ -68,4 +92,11 @@ def has_pact_with(self, provider, host_name='localhost', port=1234):
6892
consumer=self,
6993
provider=provider,
7094
host_name=host_name,
71-
port=port)
95+
port=port,
96+
log_dir=log_dir,
97+
ssl=ssl,
98+
sslcert=sslcert,
99+
sslkey=sslkey,
100+
cors=cors,
101+
pact_dir=pact_dir,
102+
version=version)

0 commit comments

Comments
 (0)