A utility library for mocking out the `requests` Python library.
Project description
Responses
=========
.. image:: https://travis-ci.org/getsentry/responses.svg?branch=master
:target: https://travis-ci.org/getsentry/responses
A utility library for mocking out the `requests` Python library.
.. note::
Responses requires Python 2.7 or newer, and requests >= 2.0
Installing
----------
``pip install responses``
Basics
------
The core of ``responses`` comes from registering mock responses:
.. code-block:: python
import responses
import requests
@responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
json={'error': 'not found'}, status=404)
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert responses.calls[0].response.text == '{"error": "not found"}'
If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise
a ``ConnectionError``:
.. code-block:: python
import responses
import requests
from requests.exceptions import ConnectionError
@responses.activate
def test_simple():
with pytest.raises(ConnectionError):
requests.get('http://twitter.com/api/1/foobar')
Lastly, you can pass an ``Exception`` as the body to trigger an error on the request:
.. code-block:: python
import responses
import requests
@responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
body=Exception('...'))
with pytest.raises(Exception):
requests.get('http://twitter.com/api/1/foobar')
Response Parameters
-------------------
Responses are automatically registered via params on ``add``, but can also be
passed directly:
.. code-block:: python
import responses
responses.add(
responses.Response(
method='GET',
url='http://example.com',
)
)
The following attributes can be passed to a Response mock:
method (``str``)
The HTTP method (GET, POST, etc).
url (``str`` or compiled regular expression)
The full resource URL.
match_querystring (``bool``)
Include the query string when matching requests.
Enabled by default if the response URL contains a query string,
disabled if it doesn't or the URL is a regular expression.
body (``str`` or ``BufferedReader``)
The response body.
json
A Python object representing the JSON response body. Automatically configures
the appropriate Content-Type.
status (``int``)
The HTTP status code.
content_type (``content_type``)
Defaults to ``text/plain``.
headers (``dict``)
Response headers.
stream (``bool``)
Disabled by default. Indicates the response should use the streaming API.
Dynamic Responses
-----------------
You can utilize callbacks to provide dynamic responses. The callback must return
a tuple of (``status``, ``headers``, ``body``).
.. code-block:: python
import json
import responses
import requests
@responses.activate
def test_calc_api():
def request_callback(request):
payload = json.loads(request.body)
resp_body = {'value': sum(payload['numbers'])}
headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
return (200, headers, json.dumps(resp_body))
responses.add_callback(
responses.POST, 'http://calc.com/sum',
callback=request_callback,
content_type='application/json',
)
resp = requests.post(
'http://calc.com/sum',
json.dumps({'numbers': [1, 2, 3]}),
headers={'content-type': 'application/json'},
)
assert resp.json() == {'value': 6}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://calc.com/sum'
assert responses.calls[0].response.text == '{"value": 6}'
assert (
responses.calls[0].response.headers['request-id'] ==
'728d329e-0e86-11e4-a748-0c84dc037c13'
)
You can also pass a compiled regex to `add_callback` to match multiple urls:
.. code-block:: python
import re, json
from functools import reduce
import responses
import requests
operators = {
'sum': lambda x, y: x+y,
'prod': lambda x, y: x*y,
'pow': lambda x, y: x**y
}
@responses.activate
def test_regex_url():
def request_callback(request):
payload = json.loads(request.body)
operator_name = request.path_url[1:]
operator = operators[operator_name]
resp_body = {'value': reduce(operator, payload['numbers'])}
headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
return (200, headers, json.dumps(resp_body))
responses.add_callback(
responses.POST,
re.compile('http://calc.com/(sum|prod|pow|unsupported)'),
callback=request_callback,
content_type='application/json',
)
resp = requests.post(
'http://calc.com/prod',
json.dumps({'numbers': [2, 3, 4]}),
headers={'content-type': 'application/json'},
)
assert resp.json() == {'value': 24}
test_regex_url()
If you want to pass extra keyword arguments to the callback function, for example when reusing
a callback function to give a slightly different result, you can use ``functools.partial``:
.. code-block:: python
from functools import partial
...
def request_callback(request, id=None):
payload = json.loads(request.body)
resp_body = {'value': sum(payload['numbers'])}
headers = {'request-id': id}
return (200, headers, json.dumps(resp_body))
responses.add_callback(
responses.POST, 'http://calc.com/sum',
callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'),
content_type='application/json',
)
Responses as a context manager
------------------------------
.. code-block:: python
import responses
import requests
def test_my_api():
with responses.RequestsMock() as rsps:
rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 200
# outside the context manager requests will hit the remote server
resp = requests.get('http://twitter.com/api/1/foobar')
resp.status_code == 404
Responses as a pytest fixture
-----------------------------
.. code-block:: python
@pytest.fixture
def mocked_responses():
with responses.RequestsMock() as rsps:
yield rsps
def test_api(mocked_responses):
mocked_responses.add(
responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 200
Assertions on declared responses
--------------------------------
When used as a context manager, Responses will, by default, raise an assertion
error if a url was registered but not accessed. This can be disabled by passing
the ``assert_all_requests_are_fired`` value:
.. code-block:: python
import responses
import requests
def test_my_api():
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
Multiple Responses
------------------
You can also add multiple responses for the same url:
.. code-block:: python
import responses
import requests
@responses.activate
def test_my_api():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar', status=500)
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 500
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 200
Using a callback to modify the response
---------------------------------------
If you use customized processing in `requests` via subclassing/mixins, or if you
have library tools that interact with `requests` at a low level, you may need
to add extended processing to the mocked Response object to fully simulate the
environment for your tests. A `response_callback` can be used, which will be
wrapped by the library before being returned to the caller. The callback
accepts a `response` as it's single argument, and is expected to return a
single `response` object.
.. code-block:: python
import responses
import requests
def response_callback(resp):
resp.callback_processed = True
return resp
with responses.RequestsMock(response_callback=response_callback) as m:
m.add(responses.GET, 'http://example.com', body=b'test')
resp = requests.get('http://example.com')
assert resp.text == "test"
assert hasattr(resp, 'callback_processed')
assert resp.callback_processed is True
Passing thru real requests
--------------------------
In some cases you may wish to allow for certain requests to pass thru responses
and hit a real server. This can be done with the 'passthru' methods:
.. code-block:: python
import responses
@responses.activate
def test_my_api():
responses.add_passthru('https://percy.io')
This will allow any requests matching that prefix, that is otherwise not registered
as a mock response, to passthru using the standard behavior.
Viewing/Modifying registered responses
--------------------------------------
Registered responses are available as a private attribute of the RequestMock
instance. It is sometimes useful for debugging purposes to view the stack of
registered responses which can be accessed via ``responses.mock._matches``.
The ``replace`` function allows a previously registered ``response`` to be
changed. The method signature is identical to ``add``. ``response``s are
identified using ``method`` and ``url``. Only the first matched ``response`` is
replaced.
.. code-block:: python
import responses
import requests
@responses.activate
def test_replace():
responses.add(responses.GET, 'http://example.org', json={'data': 1})
responses.replace(responses.GET, 'http://example.org', json={'data': 2})
resp = requests.get('http://example.org')
assert resp.json() == {'data': 2}
``remove`` takes a ``method`` and ``url`` argument and will remove *all*
matched ``response``s from the registered list.
Finally, ``clear`` will reset all registered ``response``s
Contributing
------------
Responses uses several linting and autoformatting utilities, so it's important that when
submitting patches you use the appropriate toolchain:
Clone the repository:
.. code-block:: shell
git clone https://github.com/getsentry/responses.git
Create an environment (e.g. with ``virtualenv``):
.. code-block:: shell
virtualenv .env && source .env/bin/activate
Configure development requirements:
.. code-block:: shell
make develop
=========
.. image:: https://travis-ci.org/getsentry/responses.svg?branch=master
:target: https://travis-ci.org/getsentry/responses
A utility library for mocking out the `requests` Python library.
.. note::
Responses requires Python 2.7 or newer, and requests >= 2.0
Installing
----------
``pip install responses``
Basics
------
The core of ``responses`` comes from registering mock responses:
.. code-block:: python
import responses
import requests
@responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
json={'error': 'not found'}, status=404)
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.json() == {"error": "not found"}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://twitter.com/api/1/foobar'
assert responses.calls[0].response.text == '{"error": "not found"}'
If you attempt to fetch a url which doesn't hit a match, ``responses`` will raise
a ``ConnectionError``:
.. code-block:: python
import responses
import requests
from requests.exceptions import ConnectionError
@responses.activate
def test_simple():
with pytest.raises(ConnectionError):
requests.get('http://twitter.com/api/1/foobar')
Lastly, you can pass an ``Exception`` as the body to trigger an error on the request:
.. code-block:: python
import responses
import requests
@responses.activate
def test_simple():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
body=Exception('...'))
with pytest.raises(Exception):
requests.get('http://twitter.com/api/1/foobar')
Response Parameters
-------------------
Responses are automatically registered via params on ``add``, but can also be
passed directly:
.. code-block:: python
import responses
responses.add(
responses.Response(
method='GET',
url='http://example.com',
)
)
The following attributes can be passed to a Response mock:
method (``str``)
The HTTP method (GET, POST, etc).
url (``str`` or compiled regular expression)
The full resource URL.
match_querystring (``bool``)
Include the query string when matching requests.
Enabled by default if the response URL contains a query string,
disabled if it doesn't or the URL is a regular expression.
body (``str`` or ``BufferedReader``)
The response body.
json
A Python object representing the JSON response body. Automatically configures
the appropriate Content-Type.
status (``int``)
The HTTP status code.
content_type (``content_type``)
Defaults to ``text/plain``.
headers (``dict``)
Response headers.
stream (``bool``)
Disabled by default. Indicates the response should use the streaming API.
Dynamic Responses
-----------------
You can utilize callbacks to provide dynamic responses. The callback must return
a tuple of (``status``, ``headers``, ``body``).
.. code-block:: python
import json
import responses
import requests
@responses.activate
def test_calc_api():
def request_callback(request):
payload = json.loads(request.body)
resp_body = {'value': sum(payload['numbers'])}
headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
return (200, headers, json.dumps(resp_body))
responses.add_callback(
responses.POST, 'http://calc.com/sum',
callback=request_callback,
content_type='application/json',
)
resp = requests.post(
'http://calc.com/sum',
json.dumps({'numbers': [1, 2, 3]}),
headers={'content-type': 'application/json'},
)
assert resp.json() == {'value': 6}
assert len(responses.calls) == 1
assert responses.calls[0].request.url == 'http://calc.com/sum'
assert responses.calls[0].response.text == '{"value": 6}'
assert (
responses.calls[0].response.headers['request-id'] ==
'728d329e-0e86-11e4-a748-0c84dc037c13'
)
You can also pass a compiled regex to `add_callback` to match multiple urls:
.. code-block:: python
import re, json
from functools import reduce
import responses
import requests
operators = {
'sum': lambda x, y: x+y,
'prod': lambda x, y: x*y,
'pow': lambda x, y: x**y
}
@responses.activate
def test_regex_url():
def request_callback(request):
payload = json.loads(request.body)
operator_name = request.path_url[1:]
operator = operators[operator_name]
resp_body = {'value': reduce(operator, payload['numbers'])}
headers = {'request-id': '728d329e-0e86-11e4-a748-0c84dc037c13'}
return (200, headers, json.dumps(resp_body))
responses.add_callback(
responses.POST,
re.compile('http://calc.com/(sum|prod|pow|unsupported)'),
callback=request_callback,
content_type='application/json',
)
resp = requests.post(
'http://calc.com/prod',
json.dumps({'numbers': [2, 3, 4]}),
headers={'content-type': 'application/json'},
)
assert resp.json() == {'value': 24}
test_regex_url()
If you want to pass extra keyword arguments to the callback function, for example when reusing
a callback function to give a slightly different result, you can use ``functools.partial``:
.. code-block:: python
from functools import partial
...
def request_callback(request, id=None):
payload = json.loads(request.body)
resp_body = {'value': sum(payload['numbers'])}
headers = {'request-id': id}
return (200, headers, json.dumps(resp_body))
responses.add_callback(
responses.POST, 'http://calc.com/sum',
callback=partial(request_callback, id='728d329e-0e86-11e4-a748-0c84dc037c13'),
content_type='application/json',
)
Responses as a context manager
------------------------------
.. code-block:: python
import responses
import requests
def test_my_api():
with responses.RequestsMock() as rsps:
rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 200
# outside the context manager requests will hit the remote server
resp = requests.get('http://twitter.com/api/1/foobar')
resp.status_code == 404
Responses as a pytest fixture
-----------------------------
.. code-block:: python
@pytest.fixture
def mocked_responses():
with responses.RequestsMock() as rsps:
yield rsps
def test_api(mocked_responses):
mocked_responses.add(
responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 200
Assertions on declared responses
--------------------------------
When used as a context manager, Responses will, by default, raise an assertion
error if a url was registered but not accessed. This can be disabled by passing
the ``assert_all_requests_are_fired`` value:
.. code-block:: python
import responses
import requests
def test_my_api():
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
Multiple Responses
------------------
You can also add multiple responses for the same url:
.. code-block:: python
import responses
import requests
@responses.activate
def test_my_api():
responses.add(responses.GET, 'http://twitter.com/api/1/foobar', status=500)
responses.add(responses.GET, 'http://twitter.com/api/1/foobar',
body='{}', status=200,
content_type='application/json')
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 500
resp = requests.get('http://twitter.com/api/1/foobar')
assert resp.status_code == 200
Using a callback to modify the response
---------------------------------------
If you use customized processing in `requests` via subclassing/mixins, or if you
have library tools that interact with `requests` at a low level, you may need
to add extended processing to the mocked Response object to fully simulate the
environment for your tests. A `response_callback` can be used, which will be
wrapped by the library before being returned to the caller. The callback
accepts a `response` as it's single argument, and is expected to return a
single `response` object.
.. code-block:: python
import responses
import requests
def response_callback(resp):
resp.callback_processed = True
return resp
with responses.RequestsMock(response_callback=response_callback) as m:
m.add(responses.GET, 'http://example.com', body=b'test')
resp = requests.get('http://example.com')
assert resp.text == "test"
assert hasattr(resp, 'callback_processed')
assert resp.callback_processed is True
Passing thru real requests
--------------------------
In some cases you may wish to allow for certain requests to pass thru responses
and hit a real server. This can be done with the 'passthru' methods:
.. code-block:: python
import responses
@responses.activate
def test_my_api():
responses.add_passthru('https://percy.io')
This will allow any requests matching that prefix, that is otherwise not registered
as a mock response, to passthru using the standard behavior.
Viewing/Modifying registered responses
--------------------------------------
Registered responses are available as a private attribute of the RequestMock
instance. It is sometimes useful for debugging purposes to view the stack of
registered responses which can be accessed via ``responses.mock._matches``.
The ``replace`` function allows a previously registered ``response`` to be
changed. The method signature is identical to ``add``. ``response``s are
identified using ``method`` and ``url``. Only the first matched ``response`` is
replaced.
.. code-block:: python
import responses
import requests
@responses.activate
def test_replace():
responses.add(responses.GET, 'http://example.org', json={'data': 1})
responses.replace(responses.GET, 'http://example.org', json={'data': 2})
resp = requests.get('http://example.org')
assert resp.json() == {'data': 2}
``remove`` takes a ``method`` and ``url`` argument and will remove *all*
matched ``response``s from the registered list.
Finally, ``clear`` will reset all registered ``response``s
Contributing
------------
Responses uses several linting and autoformatting utilities, so it's important that when
submitting patches you use the appropriate toolchain:
Clone the repository:
.. code-block:: shell
git clone https://github.com/getsentry/responses.git
Create an environment (e.g. with ``virtualenv``):
.. code-block:: shell
virtualenv .env && source .env/bin/activate
Configure development requirements:
.. code-block:: shell
make develop
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
responses-0.10.6.tar.gz
(22.1 kB
view hashes)
Built Distribution
Close
Hashes for responses-0.10.6-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 97193c0183d63fba8cd3a041c75464e4b09ea0aff6328800d1546598567dde0b |
|
MD5 | e9dbcd421fa17ced1f97ac54877e2a5a |
|
BLAKE2b-256 | d15ab887e89925f1de7890ef298a74438371ed4ed29b33def9e6d02dc6036fd8 |