Aims to provide an easy way to mock an entire service API based on requests-mock and a simple dict definition of a service. The idea is to mock everything at start according given rules. Then mock-services allows to start/stop http mock locally.
During our session we can:
- add rules
- permit external calls
- stop mocking
- reset rules
- restart mocking
- etc.
Note: rules urls must be regex. They always will be compiled before updating the main requests-mock urls registry.
Let's mock our favorite search engine:
>>> def fake_duckduckgo_cb(request): ... return 200, {}, 'Coincoin!' >>> rules = [ ... { ... 'text': fake_duckduckgo_cb, ... 'headers': {'Content-Type': 'text/html'}, ... 'method': 'GET', ... 'url': r'^https://duckduckgo.com/\?q=' ... }, ... ] >>> from mock_services import update_http_rules >>> update_http_rules(rules) >>> import requests >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '<!DOCTYPE html>' >>> from mock_services import start_http_mock >>> start_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content 'Coincoin!'
When the http_mock is started if you try to call an external url, it should fail:
>>> requests.get('https://www.google.com/#q=mock-services') ... ConnectionError: Connection refused: GET https://www.google.com/#q=mock-services
Then you can allow external calls if needed:
>>> from mock_services import http_mock >>> http_mock.set_allow_external(True) >>> requests.get('https://www.google.com/#q=mock-services').content[:15] '<!doctype html>'
At anytime you can stop the mocking as follow:
>>> from mock_services import stop_http_mock >>> stop_http_mock() >>> requests.get('https://duckduckgo.com/?q=mock-services').content[:15] '<!DOCTYPE html>'
Or stop mocking during a function call:
>>> start_http_mock() >>> @no_http_mock ... def please_do_not_mock_me(): ... return requests.get('https://duckduckgo.com/?q=mock-services').content[:15] == '<!DOCTYPE html>', 'mocked!' >>> please_do_not_mock_me
Or start mocking for another function call:
>>> stop_http_mock() >>> @with_http_mock ... def please_mock_me(): ... assert requests.get('https://duckduckgo.com/?q=mock-services').content == 'Coincoin', 'no mock!' >>> please_mock_me
You can add REST rules with an explicit method. It will add rules as above and automatically bind callbacks to fake a REST service.
Note: resource and id regex options are mandatory in the rules urls.
Additionally, `mock_services`_ include attrs library. It can be use for field validation as follow.
This service mock will create, get, update and delete resources for you:
>>> import attr >>> rest_rules = [ ... { ... 'method': 'LIST', ... 'url': r'^http://my_fake_service/(?P<resource>api)$' ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?P<resource>api)/(?P<id>\d+)$', ... }, ... { ... 'method': 'GET', ... 'url': r'^http://my_fake_service/(?P<resource>api)/(?P<id>\d+)/(?P<action>download)$', ... }, ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?P<resource>api)$', ... 'id_name': 'id', ... 'id_factory': int, ... 'attrs': { ... 'bar': attr.ib(), ... 'foo':attr.ib(default=True) ... } ... }, ... { ... 'method': 'PATCH', ... 'url': r'^http://my_fake_service/(?P<resource>api)/(?P<id>\d+)$', ... }, ... { ... 'method': 'DELETE', ... 'url': r'^http://my_fake_service/(?P<resource>api)/(?P<id>\d+)$' ... }, ... ] >>> from mock_services import update_rest_rules >>> update_rest_rules(rest_rules) >>> from mock_services import start_http_mock >>> start_http_mock() >>> response = requests.get('http://my_fake_service/api') >>> response.status_code 200 >>> response.json() [] >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 404 >>> import json >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({}), ... headers={'content-type': 'application/json'}) >>> response.status_code 400 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'bar': 'Python will save the world'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; 'Python will save the world.' } >>> response = requests.patch('http://my_fake_service/api/1', ... data=json.dumps({'bar': "Python will save the world. I don't know how. But it will."}), ... headers={'content-type': 'application/json'}) >>> response.status_code 200 >>> response = requests.get('http://my_fake_service/api/1') >>> response.status_code 200 >>> response.json() { 'id': 1, 'foo'; True, 'bar'; "Python will save the world. I don't know how. But it will." } >>> response = requests.delete('http://my_fake_service/api/1') >>> response.status_code 204
Is some cases you need to validate a resource against another. Then you can add global validators per endpoint as follow:
>>> from mock_services import storage >>> from mock_services.service import ResourceContext >>> from mock_services.exceptions import Http409 >>> def duplicate_foo(request): ... data = json.loads(request.body) ... ctx = ResourceContext(hostname='my_fake_service', resource='api') ... if data['foo'] in [o['foo'] for o in storage.list(ctx)]: ... raise Http409 >>> rest_rules_with_validators = [ ... { ... 'method': 'POST', ... 'url': r'^http://my_fake_service/(?P<resource>api)$', ... 'validators': [ ... duplicate_foo, ... ], ... }, ... ] >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 201 >>> response = requests.post('http://my_fake_service/api', ... data=json.dumps({'foo': 'bar'}), ... headers={'content-type': 'application/json'}) >>> response.status_code 409
Have fun in testing external APIs ;)