aresponses


Namearesponses JSON
Version 3.0.0 PyPI version JSON
download
home_pagehttps://github.com/aresponses/aresponses
SummaryAsyncio response mocking. Similar to the responses library used for 'requests'
upload_time2024-01-12 05:36:34
maintainer
docs_urlNone
authorBryce Drennan
requires_python>=3.7
license
keywords asyncio testing responses
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            

# aresponses

[![image](https://img.shields.io/pypi/v/aresponses.svg)](https://pypi.org/project/aresponses/)
[![image](https://img.shields.io/pypi/pyversions/aresponses.svg)](https://pypi.org/project/aresponses/)
[![build status](https://github.com/CircleUp/aresponses/workflows/Python%20Checks/badge.svg)](https://github.com/CircleUp/aresponses/actions?query=branch%3Amaster)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)


an asyncio testing server for mocking external services

## Features
 - Fast mocks using actual network connections
 - allows mocking some types of network issues
 - use regular expression matching for domain, path, method, or body
 - works with https requests as well (by switching them to http requests)
 - works with callables
 
## Usage

Add routes and responses via the `aresponses.add` method:

```python
def add(
    host_pattern=ANY, 
    path_pattern=ANY, 
    method_pattern=ANY, 
    response="", 
    *, 
    route=None, 
    body_pattern=ANY, m
    match_querystring=False, 
    repeat=1
    )
```

When a request is received the first matching response will be returned
and removed from the routing table.  The `response` argument can be
either a string, Response, dict, or list.  Use `aresponses.Response`
when you need to do something more complex.


**Note that version >=2.0 requires explicit assertions!**
```python
@pytest.mark.asyncio
async def test_simple(aresponses):
    aresponses.add("google.com", "/api/v1/", "GET", response="OK")
    aresponses.add('foo.com', '/', 'get', aresponses.Response(text='error', status=500))

    async with aiohttp.ClientSession() as session:
        async with session.get("http://google.com/api/v1/") as response:
            text = await response.text()
            assert text == "OK"
        
        async with session.get("https://foo.com") as response:
            text = await response.text()
            assert text == "error"

    aresponses.assert_plan_strictly_followed()
```

#### Assertions
In aresponses 1.x requests that didn't match a route stopped the event
loop and thus forced an exception.  In aresponses >2.x it's required to
make assertions at the end of the test.

There are three assertions functions provided:
- `aresponses.assert_no_unused_routes` Raises `UnusedRouteError` if all
the routes defined were not used up.
- `aresponses.assert_called_in_order` - Raises `UnorderedRouteCallError`
if the routes weren't called in the order they were defined.
- `aresponses.assert_all_requests_matched` - Raises `NoRouteFoundError`
if any requests were made that didn't match to a route.  It's likely
but not guaranteed that your code will throw an exception in this
situation before the assertion is reached.

Instead of calling these individually, **it's recommended to call
`aresponses.assert_plan_strictly_followed()` at the end of each test as
it runs all three of the above assertions.**


#### Regex and Repeat
`host_pattern`, `path_pattern`, `method_pattern` and `body_pattern` may
be either strings (exact match) or regular expressions.

The repeat argument permits a route to be used multiple times.

If you want to just blanket mock a service, without concern for how many
times its called you could set repeat to a large number and not call
`aresponses.assert_plan_strictly_followed` or
`arespones.assert_no_unused_routes`.

```python
@pytest.mark.asyncio
async def test_regex_repetition(aresponses):
    aresponses.add(re.compile(r".*\.?google\.com"), response="OK", repeat=2)

    async with aiohttp.ClientSession() as session:
        async with session.get("http://google.com") as response:
            text = await response.text()
            assert text == "OK"

        async with session.get("http://api.google.com") as response:
            text = await response.text()
            assert text == "OK"

    aresponses.assert_plan_strictly_followed()
```

#### Json Responses
As a convenience, if a dict or list is passed to `response` then it will
create a json response. A `aiohttp.web_response.json_response` object
can be used for more complex situations.

```python
@pytest.mark.asyncio
async def test_json(aresponses):
    aresponses.add("google.com", "/api/v1/", "GET", response={"status": "OK"})

    async with aiohttp.ClientSession() as session:
        async with session.get("http://google.com/api/v1/") as response:
            assert {"status": "OK"} == await response.json()

    aresponses.assert_plan_strictly_followed()
```

#### Custom Handler

Custom functions can be used for whatever other complex logic is
desired. In example below the handler is set to repeat infinitely
and always return 500.

```python
import math

@pytest.mark.asyncio
async def test_handler(aresponses):
    def break_everything(request):
        return aresponses.Response(status=500, text=str(request.url))

    aresponses.add(response=break_everything, repeat=math.inf)

    async with aiohttp.ClientSession() as session:
        async with session.get("http://google.com/api/v1/") as response:
            assert response.status == 500
```


#### Passthrough
Pass `aresponses.passthrough` into the response argument to allow a
request to bypass mocking.

```python
    aresponses.add('httpstat.us', '/200', 'get', aresponses.passthrough)
```

#### Inspecting history
History of calls can be inspected via `aresponses.history` which returns
the namedTuple `RoutingLog(request, route, response)`

```python
@pytest.mark.asyncio
async def test_history(aresponses):
    aresponses.add(response=aresponses.Response(text="hi"), repeat=2)

    async with aiohttp.ClientSession() as session:
        async with session.get("http://foo.com/b") as response:
            await response.text()
        async with session.get("http://bar.com/a") as response:
            await response.text()

    assert len(aresponses.history) == 2
    assert aresponses.history[0].request.host == "foo.com"
    assert aresponses.history[1].request.host == "bar.com"
    assert "Route(" in repr(aresponses.history[0].route)
    aresponses.assert_plan_strictly_followed()
```

#### Context manager usage
```python
import aiohttp
import pytest
import aresponses


@pytest.mark.asyncio
async def test_foo(event_loop):
    async with aresponses.ResponsesMockServer(loop=event_loop) as arsps:
        arsps.add('foo.com', '/', 'get', 'hi there!!')
        arsps.add(arsps.ANY, '/', 'get', arsps.Response(text='hey!'))
        
        async with aiohttp.ClientSession(loop=event_loop) as session:
            async with session.get('http://foo.com') as response:
                text = await response.text()
                assert text == 'hi'
            
            async with session.get('https://google.com') as response:
                text = await response.text()
                assert text == 'hey!'
        
```

#### working with [pytest-aiohttp](https://github.com/aio-libs/pytest-aiohttp)

If you need to use aresponses together with pytest-aiohttp, you should re-initialize main aresponses fixture with `loop` fixture
```python
from aresponses import ResponsesMockServer

@pytest.fixture
async def aresponses(loop):
    async with ResponsesMockServer(loop=loop) as server:
        yield server
```

If you're trying to use the `aiohttp_client` test fixture then you'll need to mock out the aiohttp `loop` fixture
instead:
```python
@pytest.fixture
def loop(event_loop):
    """replace aiohttp loop fixture with pytest-asyncio fixture"""
    return event_loop
```

## Contributing

### Dev environment setup
  - **install pyenv and pyenv-virtualenv**  - Makes it easy to install specific versions of python and switch between them. Make sure you install the virtualenv bash hook
  - `git clone` the repo and `cd` into it.
  - `make init` - installs proper version of python, creates the virtual environment, activates it and installs all the requirements
  
### Submitting a feature request  
  - **`git checkout -b my-feature-branch`** 
  - **make some cool changes**
  - **`make autoformat`**
  - **`make test`**
  - **`make lint`**
  - **create pull request**

### Updating package on pypi
  - `make deploy`


## Changelog

#### 3.0.0
- fix: start using `asyncio.get_running_loop()` instead of `event_loop` per the error:
    ```
    PytestDeprecationWarning: aresponses is asynchronous and explicitly requests the "event_loop" fixture. 
    Asynchronous fixtures and test functions should use "asyncio.get_running_loop()" instead.
    ```
- drop support for python 3.6
- add comprehensive matrix testing of all supported python and aiohttp versions
- tighten up the setup.py requirements

#### 2.1.6
- fix: incorrect pytest plugin entrypoint name (#72)

#### 2.1.5
- support asyncio_mode = strict (#68)
 
#### 2.1.4
- fix: don't assume utf8 request contents

#### 2.1.3
- accidental no-op release

#### 2.1.2
- documentation: add pypi documentation

#### 2.1.1

- bugfix: RecursionError when aresponses is used in more than 1000 tests (#63)

#### 2.1.0
- feature: add convenience method `add_local_passthrough`
- bugfix: fix https subrequest mocks. support aiohttp_client compatibility

#### 2.0.2
- bugfix: ensure request body is available in history

#### 2.0.0
**Warning! Breaking Changes!**
- breaking change: require explicit assertions for test failures
- feature: autocomplete works in intellij/pycharm
- feature: can match on body of request
- feature: store calls made
- feature: repeated responses
- bugfix: no longer stops event loop
- feature: if dict or list is passed into `response`, a json response
will be generated


#### 1.1.2
- make passthrough feature work with binary data

#### 1.1.1
- regex fix for Python 3.7.0

#### 1.1.0
- Added passthrough option to permit live network calls
- Added example of using a callable as a response

#### 1.0.0

- Added an optional `match_querystring` argument that lets you match on querystring as well


## Contributors
* Bryce Drennan, CircleUp <aresponses@brycedrennan.com>
* Marco Castelluccio, Mozilla <mcastelluccio@mozilla.com>
* Jesse Vogt, CircleUp <jesse.vogt@gmail.com>
* Pavol Vargovcik, Kiwi.com <pavol.vargovcik@gmail.com>



            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/aresponses/aresponses",
    "name": "aresponses",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "asyncio,testing,responses",
    "author": "Bryce Drennan",
    "author_email": "aresponses@brycedrennan.com",
    "download_url": "https://files.pythonhosted.org/packages/ed/96/99b3f8c2ad6a5547bcb4726e572513564c23d4a3a3e21a03038dd3a84fe4/aresponses-3.0.0.tar.gz",
    "platform": null,
    "description": "\n\n# aresponses\n\n[![image](https://img.shields.io/pypi/v/aresponses.svg)](https://pypi.org/project/aresponses/)\n[![image](https://img.shields.io/pypi/pyversions/aresponses.svg)](https://pypi.org/project/aresponses/)\n[![build status](https://github.com/CircleUp/aresponses/workflows/Python%20Checks/badge.svg)](https://github.com/CircleUp/aresponses/actions?query=branch%3Amaster)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)\n\n\nan asyncio testing server for mocking external services\n\n## Features\n - Fast mocks using actual network connections\n - allows mocking some types of network issues\n - use regular expression matching for domain, path, method, or body\n - works with https requests as well (by switching them to http requests)\n - works with callables\n \n## Usage\n\nAdd routes and responses via the `aresponses.add` method:\n\n```python\ndef add(\n    host_pattern=ANY, \n    path_pattern=ANY, \n    method_pattern=ANY, \n    response=\"\", \n    *, \n    route=None, \n    body_pattern=ANY, m\n    match_querystring=False, \n    repeat=1\n    )\n```\n\nWhen a request is received the first matching response will be returned\nand removed from the routing table.  The `response` argument can be\neither a string, Response, dict, or list.  Use `aresponses.Response`\nwhen you need to do something more complex.\n\n\n**Note that version >=2.0 requires explicit assertions!**\n```python\n@pytest.mark.asyncio\nasync def test_simple(aresponses):\n    aresponses.add(\"google.com\", \"/api/v1/\", \"GET\", response=\"OK\")\n    aresponses.add('foo.com', '/', 'get', aresponses.Response(text='error', status=500))\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(\"http://google.com/api/v1/\") as response:\n            text = await response.text()\n            assert text == \"OK\"\n        \n        async with session.get(\"https://foo.com\") as response:\n            text = await response.text()\n            assert text == \"error\"\n\n    aresponses.assert_plan_strictly_followed()\n```\n\n#### Assertions\nIn aresponses 1.x requests that didn't match a route stopped the event\nloop and thus forced an exception.  In aresponses >2.x it's required to\nmake assertions at the end of the test.\n\nThere are three assertions functions provided:\n- `aresponses.assert_no_unused_routes` Raises `UnusedRouteError` if all\nthe routes defined were not used up.\n- `aresponses.assert_called_in_order` - Raises `UnorderedRouteCallError`\nif the routes weren't called in the order they were defined.\n- `aresponses.assert_all_requests_matched` - Raises `NoRouteFoundError`\nif any requests were made that didn't match to a route.  It's likely\nbut not guaranteed that your code will throw an exception in this\nsituation before the assertion is reached.\n\nInstead of calling these individually, **it's recommended to call\n`aresponses.assert_plan_strictly_followed()` at the end of each test as\nit runs all three of the above assertions.**\n\n\n#### Regex and Repeat\n`host_pattern`, `path_pattern`, `method_pattern` and `body_pattern` may\nbe either strings (exact match) or regular expressions.\n\nThe repeat argument permits a route to be used multiple times.\n\nIf you want to just blanket mock a service, without concern for how many\ntimes its called you could set repeat to a large number and not call\n`aresponses.assert_plan_strictly_followed` or\n`arespones.assert_no_unused_routes`.\n\n```python\n@pytest.mark.asyncio\nasync def test_regex_repetition(aresponses):\n    aresponses.add(re.compile(r\".*\\.?google\\.com\"), response=\"OK\", repeat=2)\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(\"http://google.com\") as response:\n            text = await response.text()\n            assert text == \"OK\"\n\n        async with session.get(\"http://api.google.com\") as response:\n            text = await response.text()\n            assert text == \"OK\"\n\n    aresponses.assert_plan_strictly_followed()\n```\n\n#### Json Responses\nAs a convenience, if a dict or list is passed to `response` then it will\ncreate a json response. A `aiohttp.web_response.json_response` object\ncan be used for more complex situations.\n\n```python\n@pytest.mark.asyncio\nasync def test_json(aresponses):\n    aresponses.add(\"google.com\", \"/api/v1/\", \"GET\", response={\"status\": \"OK\"})\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(\"http://google.com/api/v1/\") as response:\n            assert {\"status\": \"OK\"} == await response.json()\n\n    aresponses.assert_plan_strictly_followed()\n```\n\n#### Custom Handler\n\nCustom functions can be used for whatever other complex logic is\ndesired. In example below the handler is set to repeat infinitely\nand always return 500.\n\n```python\nimport math\n\n@pytest.mark.asyncio\nasync def test_handler(aresponses):\n    def break_everything(request):\n        return aresponses.Response(status=500, text=str(request.url))\n\n    aresponses.add(response=break_everything, repeat=math.inf)\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(\"http://google.com/api/v1/\") as response:\n            assert response.status == 500\n```\n\n\n#### Passthrough\nPass `aresponses.passthrough` into the response argument to allow a\nrequest to bypass mocking.\n\n```python\n    aresponses.add('httpstat.us', '/200', 'get', aresponses.passthrough)\n```\n\n#### Inspecting history\nHistory of calls can be inspected via `aresponses.history` which returns\nthe namedTuple `RoutingLog(request, route, response)`\n\n```python\n@pytest.mark.asyncio\nasync def test_history(aresponses):\n    aresponses.add(response=aresponses.Response(text=\"hi\"), repeat=2)\n\n    async with aiohttp.ClientSession() as session:\n        async with session.get(\"http://foo.com/b\") as response:\n            await response.text()\n        async with session.get(\"http://bar.com/a\") as response:\n            await response.text()\n\n    assert len(aresponses.history) == 2\n    assert aresponses.history[0].request.host == \"foo.com\"\n    assert aresponses.history[1].request.host == \"bar.com\"\n    assert \"Route(\" in repr(aresponses.history[0].route)\n    aresponses.assert_plan_strictly_followed()\n```\n\n#### Context manager usage\n```python\nimport aiohttp\nimport pytest\nimport aresponses\n\n\n@pytest.mark.asyncio\nasync def test_foo(event_loop):\n    async with aresponses.ResponsesMockServer(loop=event_loop) as arsps:\n        arsps.add('foo.com', '/', 'get', 'hi there!!')\n        arsps.add(arsps.ANY, '/', 'get', arsps.Response(text='hey!'))\n        \n        async with aiohttp.ClientSession(loop=event_loop) as session:\n            async with session.get('http://foo.com') as response:\n                text = await response.text()\n                assert text == 'hi'\n            \n            async with session.get('https://google.com') as response:\n                text = await response.text()\n                assert text == 'hey!'\n        \n```\n\n#### working with [pytest-aiohttp](https://github.com/aio-libs/pytest-aiohttp)\n\nIf you need to use aresponses together with pytest-aiohttp, you should re-initialize main aresponses fixture with `loop` fixture\n```python\nfrom aresponses import ResponsesMockServer\n\n@pytest.fixture\nasync def aresponses(loop):\n    async with ResponsesMockServer(loop=loop) as server:\n        yield server\n```\n\nIf you're trying to use the `aiohttp_client` test fixture then you'll need to mock out the aiohttp `loop` fixture\ninstead:\n```python\n@pytest.fixture\ndef loop(event_loop):\n    \"\"\"replace aiohttp loop fixture with pytest-asyncio fixture\"\"\"\n    return event_loop\n```\n\n## Contributing\n\n### Dev environment setup\n  - **install pyenv and pyenv-virtualenv**  - Makes it easy to install specific versions of python and switch between them. Make sure you install the virtualenv bash hook\n  - `git clone` the repo and `cd` into it.\n  - `make init` - installs proper version of python, creates the virtual environment, activates it and installs all the requirements\n  \n### Submitting a feature request  \n  - **`git checkout -b my-feature-branch`** \n  - **make some cool changes**\n  - **`make autoformat`**\n  - **`make test`**\n  - **`make lint`**\n  - **create pull request**\n\n### Updating package on pypi\n  - `make deploy`\n\n\n## Changelog\n\n#### 3.0.0\n- fix: start using `asyncio.get_running_loop()` instead of `event_loop` per the error:\n    ```\n    PytestDeprecationWarning: aresponses is asynchronous and explicitly requests the \"event_loop\" fixture. \n    Asynchronous fixtures and test functions should use \"asyncio.get_running_loop()\" instead.\n    ```\n- drop support for python 3.6\n- add comprehensive matrix testing of all supported python and aiohttp versions\n- tighten up the setup.py requirements\n\n#### 2.1.6\n- fix: incorrect pytest plugin entrypoint name (#72)\n\n#### 2.1.5\n- support asyncio_mode = strict (#68)\n \n#### 2.1.4\n- fix: don't assume utf8 request contents\n\n#### 2.1.3\n- accidental no-op release\n\n#### 2.1.2\n- documentation: add pypi documentation\n\n#### 2.1.1\n\n- bugfix: RecursionError when aresponses is used in more than 1000 tests (#63)\n\n#### 2.1.0\n- feature: add convenience method `add_local_passthrough`\n- bugfix: fix https subrequest mocks. support aiohttp_client compatibility\n\n#### 2.0.2\n- bugfix: ensure request body is available in history\n\n#### 2.0.0\n**Warning! Breaking Changes!**\n- breaking change: require explicit assertions for test failures\n- feature: autocomplete works in intellij/pycharm\n- feature: can match on body of request\n- feature: store calls made\n- feature: repeated responses\n- bugfix: no longer stops event loop\n- feature: if dict or list is passed into `response`, a json response\nwill be generated\n\n\n#### 1.1.2\n- make passthrough feature work with binary data\n\n#### 1.1.1\n- regex fix for Python 3.7.0\n\n#### 1.1.0\n- Added passthrough option to permit live network calls\n- Added example of using a callable as a response\n\n#### 1.0.0\n\n- Added an optional `match_querystring` argument that lets you match on querystring as well\n\n\n## Contributors\n* Bryce Drennan, CircleUp <aresponses@brycedrennan.com>\n* Marco Castelluccio, Mozilla <mcastelluccio@mozilla.com>\n* Jesse Vogt, CircleUp <jesse.vogt@gmail.com>\n* Pavol Vargovcik, Kiwi.com <pavol.vargovcik@gmail.com>\n\n\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Asyncio response mocking. Similar to the responses library used for 'requests'",
    "version": "3.0.0",
    "project_urls": {
        "Download": "https://github.com/aresponses/aresponses/tarball/3.0.0",
        "Homepage": "https://github.com/aresponses/aresponses"
    },
    "split_keywords": [
        "asyncio",
        "testing",
        "responses"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "00e6554d29eb029da3b0ce3642483fce758043f3132b94ab54ecd1053eee29ee",
                "md5": "53878fa8b0fcc208cbbece5575f2e5aa",
                "sha256": "8093ab4758eb4aba91c765a50295b269ecfc0a9e7c7158954760bc0c23503970"
            },
            "downloads": -1,
            "filename": "aresponses-3.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "53878fa8b0fcc208cbbece5575f2e5aa",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 9879,
            "upload_time": "2024-01-12T05:36:33",
            "upload_time_iso_8601": "2024-01-12T05:36:33.097314Z",
            "url": "https://files.pythonhosted.org/packages/00/e6/554d29eb029da3b0ce3642483fce758043f3132b94ab54ecd1053eee29ee/aresponses-3.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ed9699b3f8c2ad6a5547bcb4726e572513564c23d4a3a3e21a03038dd3a84fe4",
                "md5": "a6a9f29a1a878140bb354d5ce82b7eca",
                "sha256": "8731d0609fe4c954e21f17753dc868dca9e2e002b020a33dc9212004599b11e7"
            },
            "downloads": -1,
            "filename": "aresponses-3.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "a6a9f29a1a878140bb354d5ce82b7eca",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 14796,
            "upload_time": "2024-01-12T05:36:34",
            "upload_time_iso_8601": "2024-01-12T05:36:34.323115Z",
            "url": "https://files.pythonhosted.org/packages/ed/96/99b3f8c2ad6a5547bcb4726e572513564c23d4a3a3e21a03038dd3a84fe4/aresponses-3.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-01-12 05:36:34",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "aresponses",
    "github_project": "aresponses",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "aresponses"
}
        
Elapsed time: 0.21548s