├── conftest.py ├── requirements.txt ├── dev-requirements.txt ├── .gitignore ├── AUTHORS.rst ├── setup.cfg ├── CHANGELOG ├── setup.py ├── CONTRIBUTING.rst ├── README.md ├── aiohttpretty.py └── tests └── test_general.py /conftest.py: -------------------------------------------------------------------------------- 1 | # dummy file so pytest knows this is the project root 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp>=3.5.4 2 | furl==2.0.0 3 | multidict==4.5.2 4 | yarl==1.3.0 5 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | 3 | flake8==3.7.6 4 | pytest==4.3.1 5 | pytest-cov==2.6.1 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .git 3 | .idea 4 | **/*.ipynb* 5 | **/*.pyc 6 | **/__pycache__ 7 | local.py 8 | .coverage 9 | .cache 10 | .eggs 11 | .python-version 12 | *.egg-info/ 13 | build/ 14 | dist/ -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | 6 | Contributors 7 | ------------ 8 | 9 | - Chris Seto `@chrisseto `_ 10 | - Fitz Elliott `@felliott `_ 11 | - John Tordoff `@Johnetordoff `_ 12 | - Joshua Carp `@jmcarp `_ 13 | - Longze Chen `@cslzchen `_ 14 | - Lyndsy Simon `@lyndsysimon `_ 15 | - Michael Haselton `@icereval `_ 16 | - Mikhail Mezyakov `@aluminiumgeek `_ 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # E501: Line too long 2 | # E127: continuation line over-indented for visual indent 3 | # E128: continuation line under-indented for visual indent 4 | # E265: block comment should start with # 5 | # E301: expected 1 blank line, found 0 6 | # E302: expected 2 blank lines, found 0 7 | # F403: unable to detect undefined names 8 | # W292: no newline at end of file 9 | # E999: invalid syntax (async def) 10 | [flake8] 11 | ignore = E501,E127,E128,E265,E301,E302,F403,W292,E999 12 | max-line-length = 100 13 | exclude = .ropeproject,tests/* 14 | 15 | [tool:pytest] 16 | addopts = --cov-report term-missing --cov aiohttpretty 17 | 18 | # turn on branch coverage testing 19 | [coverage:run] 20 | branch = True 21 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | ********* 2 | ChangeLog 3 | ********* 4 | 5 | 0.1.0 (2017-02-10) 6 | ================== 7 | - aiohttpretty will now install itself into your site_packages. (thanks, @aluminiumgeek!) 8 | - The error message for unhandled requests now show the query parameters in the request. No more 9 | wondering why it's complaining about unhandled url 'http://foo.com/', when it's cleary registered 10 | RIGHT THERE! (thanks, @Johnetordoff!) 11 | - aiohttpretty now has tests! (thanks, @Johnetordoff!) 12 | - Added a CONTRIBUTING.rst and AUTHORS.rst. 13 | 14 | 0.0.2 (2016-05-03) 15 | ================== 16 | - aiohttpretty now mocks aiohttp.ClientSession._request. aiohttp.request has been deprecated and 17 | calls ._request directly. aiohttp.ClientSession.request wraps _.request with a context wrapper. 18 | - bump minimum aiohttp dependency to v0.18.0 19 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | 4 | def parse_requirements(requirements): 5 | with open(requirements) as f: 6 | return [l.strip('\n') for l in f if l.strip('\n') and not l.startswith('#')] 7 | 8 | 9 | requirements = parse_requirements('requirements.txt') 10 | 11 | setup( 12 | name='aiohttpretty', 13 | version='19.0.0', 14 | description='AioHTTPretty', 15 | author='Center for Open Science', 16 | author_email='contact@cos.io', 17 | url='https://github.com/CenterForOpenScience/aiohttpretty', 18 | packages=find_packages(exclude=('tests*', )), 19 | py_modules=['aiohttpretty'], 20 | include_package_data=True, 21 | install_requires=requirements, 22 | zip_safe=False, 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'Natural Language :: English', 27 | 'Programming Language :: Python :: 3.5', 28 | 'Programming Language :: Python :: 3.6', 29 | ], 30 | ) 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | *********************** 2 | Contributing guidelines 3 | *********************** 4 | 5 | - `PEP 8`_, when sensible. 6 | - Write tests and docs for new features. 7 | - Please update AUTHORS.rst when you contribute. 8 | - Max line is set to 100 characters. 9 | - Tests are not linted, but don't be terrible. 10 | 11 | .. _`PEP 8`: http://www.python.org/dev/peps/pep-0008/ 12 | 13 | 14 | Imports should be ordered in pep8 style but ordered by line length. 15 | 16 | .. code-block:: python 17 | 18 | # Good! 19 | import json 20 | import asyncio 21 | import unittest 22 | 23 | import pytest 24 | from furl import furl 25 | 26 | import aiohttpretty 27 | 28 | # Bad 29 | import pytest 30 | import aiohttpretty 31 | import unittest 32 | import asyncio 33 | import json 34 | 35 | 36 | ``aiohttpretty`` expects pretty pull requests, `clean commit histories`_, and meaningful commit messages. 37 | 38 | - Make sure to rebase (``git rebase -i ``) to remove pointless commits. Pointless commits include but are not limited to: 39 | 40 | - Fix flake errors 41 | - Fix typo 42 | - Fix test 43 | 44 | - Follow the guidelines for commit messages in the above 45 | 46 | - Don't worry about new lines between bullet points 47 | 48 | .. _`clean commit histories`: http://justinhileman.info/article/changing-history/ 49 | 50 | 51 | ``aiohttpretty`` uses `semantic versioning`_ ``..`` 52 | 53 | - Patches are reserved for hotfixes only 54 | - Minor versions are for **adding** new functionality or fields 55 | - Minor versions **will not** contain breaking changes to the existing API 56 | 57 | - Any changes **must** be backwards compatible 58 | 59 | - Major versions **may** contain breaking changes to the existing API 60 | 61 | .. _`semantic versioning`: http://semver.org/ 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aiohttpretty 2 | 3 | A simple ``asyncio`` compatible ``httpretty`` mock using ``aiohttp``. Tells ``aiohttp`` to return bespoke payloads from certain urls. If ``aiohttp`` is used to access a url that isn't mocked, throws an error to prevent tests from making live requests. 4 | 5 | 6 | ## Synopsis 7 | 8 | ``` 9 | import myproject 10 | 11 | import pytest 12 | import aiohttpretty 13 | 14 | 15 | @pytest.mark.asyncio 16 | async def test_get_keys_foo(): 17 | good_response = {'dog': 'woof', 'duck': 'quack'} 18 | good_url = 'http://example.com/dict' 19 | aiohttpretty.register_json_uri('GET', good_url, body=good_response) 20 | 21 | bad_response = ['dog', 'duck'] 22 | bad_url = 'http://example.com/list' 23 | aiohttpretty.register_json_uri('GET', bad_url, body=bad_response) 24 | 25 | aiohttpretty.activate() 26 | 27 | # .get_keys_from_url() calls .keys() on response 28 | keys_from_good = await myproject.get_keys_from_url(good_url) 29 | assert keys_from_good == ['dog', 'duck'] 30 | 31 | with pytest.raises(exceptions.AttributeError) as exc: 32 | await myproject.get_keys_from_url(bad_url) 33 | 34 | # aiohttpretty will die screaming that this url hasn't been mocked 35 | # await myproject.get_keys_from_url('http://example.com/unhandled') 36 | 37 | aiohttpretty.deactivate() 38 | ``` 39 | 40 | 41 | ## Methods 42 | 43 | #### `.register_uri(method, uri, **options)` 44 | 45 | Register the specified request with aiohttpretty. When `aiohttp.request` is called, `aiohttpretty` will look for a request that matches in its registry. If it finds one, it returns a response defined by the parameters given in `options`. If no matching request is found, `aiohttpretty` will throw an error. The HTTP method, uri, and query parameters must all match to be found. 46 | 47 | `method`: HTTP method to be issued against the `uri`. 48 | 49 | `uri`: The uri to be mocked. 50 | 51 | `options`: modifiers to the expected request and the mock response 52 | 53 | * `params`: Affects the *request*. These will be added to the registered uri. 54 | 55 | * `responses`: Affects the *response*. A list of dicts containing one or more of the following parameters. Each call to the uri will return the next response in the sequence. 56 | 57 | * `status`: Affects the *response*. The HTTP status code of the response. Defaults to 200. 58 | 59 | * `reason`: Affects the *response*. The HTTP reason phrase of the response. Defaults to `''` (an empty string). 60 | 61 | * `auto_length`: Affects the *response*. Generate the `Content-Length` header for the response based on the size of the `body`. If the same header is provided in the `headers` option, the value will be overwritten by the auto-generated value. 62 | 63 | * `headers`: Affects the *response*. A dict of headers to be included with the response. Default is *no headers*. 64 | 65 | * `body`: Affects the *response*. The content to be returned when `.read()` is called on the response object. Must be either `bytes`, `str`, or `instanceOf(asyncio.StreamReader)`. 66 | 67 | 68 | #### `.register_json_uri(method, uri, **options)` 69 | 70 | Same as `.register_uri` but automatically adds a `Content-Type: application/json` header to the response (though this can be overwritten if an explicit `Content-Type` is passed in the `headers` kwarg). Will also json encode the data structure given in the `body` kwarg. 71 | 72 | 73 | #### `.fake_request(method, uri, **kwargs)` 74 | 75 | `aiohttpretty`'s fake implementation of aiohttp's request method. Takes the same parameters, but only uses `method`, `uri`, and `params` to lookup the mocked response in the registry. If the `data` kwarg is set and is an instance of `asyncio.StreamReader`, `.fake_request()` will read the stream to exhaustion to mimic its consumption. 76 | 77 | 78 | #### `.activate()` 79 | 80 | Replaces `aiohttp.ClientSession._request` with `.fake_request()`. The original implementation is saved. 81 | 82 | 83 | #### `.deactivate()` 84 | 85 | Restores `aiohttp.ClientSession._request` to the saved implementation. 86 | 87 | 88 | #### `.clear()` 89 | 90 | Purge the registry and the list of intercepted calls. 91 | 92 | 93 | #### `.has_call(uri, check_params=True)` 94 | 95 | Checks to see if the given uri was called during the test. By default, will verify that the query params match up. Setting `check_params` to `False` will strip params from the *registered* uri, not the passed-in uri. 96 | 97 | 98 | ## Other 99 | 100 | ### pytest marker 101 | 102 | To simplify usage of `aiohttpretty` in tests, you can make a pytest marker that will automatically activate/deactivate `aiohttpretty` for the scope of a test. To do so, add the following to your `conftest.py`: 103 | 104 | ``` 105 | import aiohttpretty 106 | 107 | def pytest_configure(config): 108 | config.addinivalue_line( 109 | 'markers', 110 | 'aiohttpretty: mark tests to activate aiohttpretty' 111 | ) 112 | 113 | def pytest_runtest_setup(item): 114 | marker = item.get_marker('aiohttpretty') 115 | if marker is not None: 116 | aiohttpretty.clear() 117 | aiohttpretty.activate() 118 | 119 | def pytest_runtest_teardown(item, nextitem): 120 | marker = item.get_marker('aiohttpretty') 121 | if marker is not None: 122 | aiohttpretty.deactivate() 123 | ``` 124 | 125 | Then add `@pytest.mark.aiohttpretty` before `async def test_foo`. 126 | -------------------------------------------------------------------------------- /aiohttpretty.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import copy 3 | import json 4 | import asyncio 5 | import collections 6 | from unittest.mock import Mock 7 | 8 | from yarl import URL 9 | from furl import furl 10 | from multidict import CIMultiDict 11 | from aiohttp import ClientSession 12 | from aiohttp.helpers import TimerNoop 13 | from aiohttp.streams import StreamReader 14 | from aiohttp.client import ClientResponse 15 | from aiohttp.base_protocol import BaseProtocol 16 | 17 | 18 | # TODO: Add static type checker with `mypy` 19 | # TODO: Update docstr for most methods 20 | 21 | class ImmutableFurl: 22 | 23 | def __init__(self, url, params=None): 24 | self._url = url 25 | self._furl = furl(url) 26 | self._params = furl(url).args 27 | self._furl.set(args={}) 28 | 29 | params = params or {} 30 | for (k, v) in params.items(): 31 | self._params.add(k, v) 32 | 33 | def with_out_params(self): 34 | return ImmutableFurl(self.url) 35 | 36 | @property 37 | def url(self): 38 | return self._furl.url 39 | 40 | @property 41 | def params(self): 42 | return self._params 43 | 44 | def __eq__(self, other): 45 | return hash(self) == hash(other) 46 | 47 | def __hash__(self): 48 | return hash(self.url + ''.join([ 49 | self.params[x] or '' 50 | for x in sorted(self.params) 51 | ])) 52 | 53 | 54 | class _MockStream(StreamReader): 55 | 56 | def __init__(self, data): 57 | 58 | protocol = BaseProtocol(Mock()) 59 | super().__init__(protocol) 60 | 61 | self.size = len(data) 62 | self.feed_data(data) 63 | self.feed_eof() 64 | 65 | 66 | def _wrap_content_stream(content): 67 | 68 | if isinstance(content, str): 69 | content = content.encode('utf-8') 70 | 71 | if isinstance(content, bytes): 72 | return _MockStream(content) 73 | 74 | if hasattr(content, 'read') and asyncio.iscoroutinefunction(content.read): 75 | return content 76 | 77 | raise TypeError('Content must be of type bytes or str, or implement the stream interface.') 78 | 79 | 80 | def build_raw_headers(headers): 81 | """Convert a dict of headers to a tuple of tuples. Mimics the format of ClientResponse. 82 | """ 83 | raw_headers = [] 84 | for k, v in headers.items(): 85 | raw_headers.append((k.encode('utf8'), v.encode('utf8'))) 86 | return tuple(raw_headers) 87 | 88 | 89 | class _AioHttPretty: 90 | 91 | def __init__(self): 92 | 93 | self.calls = [] 94 | self.registry = {} 95 | self.request = None 96 | 97 | def make_call(self, **kwargs): 98 | return kwargs 99 | 100 | async def process_request(self, **kwargs): 101 | """Process request options as if the request was actually executed. 102 | """ 103 | data = kwargs.get('data') 104 | if isinstance(data, asyncio.StreamReader): 105 | await data.read() 106 | 107 | async def fake_request(self, method, uri, **kwargs): 108 | 109 | params = kwargs.get('params', None) 110 | url = ImmutableFurl(uri, params=params) 111 | 112 | try: 113 | response = self.registry[(method, url)] 114 | except KeyError: 115 | raise Exception( 116 | 'No URLs matching {method} {uri} with params {url.params}. ' 117 | 'Not making request. Go fix your test.'.format(**locals()) 118 | ) 119 | 120 | if isinstance(response, collections.Sequence): 121 | try: 122 | response = response.pop(0) 123 | except IndexError: 124 | raise Exception('No responses left.') 125 | 126 | await self.process_request(**kwargs) 127 | self.calls.append(self.make_call( 128 | method=method, 129 | uri=ImmutableFurl(uri, params=kwargs.pop('params', None)), 130 | **kwargs 131 | )) 132 | 133 | # For how to mock `ClientResponse` for `aiohttp>=3.1.0`, refer to the following link: 134 | # https://github.com/pnuckowski/aioresponses/blob/master/aioresponses/core.py#L129-L156 135 | # Here is the original commit that added support for `aiohttp>=3.1.0`: 136 | # https://github.com/pnuckowski/aioresponses/commit/87cf1041179139ad78a2554713b615684b8987db 137 | loop = Mock() 138 | loop.get_debug = Mock() 139 | loop.get_debug.return_value = True 140 | resp_kwargs = { 141 | 'request_info': Mock(), 142 | 'writer': Mock(), 143 | 'continue100': None, 144 | 'timer': TimerNoop(), 145 | 'traces': [], 146 | 'loop': loop, 147 | 'session': None, 148 | } 149 | 150 | # When init `ClientResponse`, the second parameter must be of type `yarl.URL` 151 | # TODO: Integrate a property of this type to `ImmutableFurl` 152 | y_url = URL(uri) 153 | mock_response = ClientResponse(method, y_url, **resp_kwargs) 154 | 155 | # TODO: can we simplify this `_wrap_content_stream()` 156 | mock_response.content = _wrap_content_stream(response.get('body', 'aiohttpretty')) 157 | 158 | # Build response headers manually 159 | headers = CIMultiDict(response.get('headers', {})) 160 | if response.get('auto_length'): 161 | # Calculate and overwrite the "Content-Length" header on-the-fly if Waterbutler tests 162 | # call `aiohttpretty.register_uri()` with `auto_length=True` 163 | headers.update({'Content-Length': str(mock_response.content.size)}) 164 | raw_headers = build_raw_headers(headers) 165 | # Use `._headers` and `._raw_headers` for `aiohttp>=3.3.0`, compared to using `.headers` and 166 | # `.raw_headers` for `3.3.0>aiohttp>=3.1.0` 167 | mock_response._headers = headers 168 | mock_response._raw_headers = raw_headers 169 | 170 | # Set response status and reason 171 | mock_response.status = response.get('status', 200) 172 | mock_response.reason = response.get('reason', '') 173 | 174 | return mock_response 175 | 176 | def register_uri(self, method, uri, **options): 177 | if any(x.get('params') for x in options.get('responses', [])): 178 | raise ValueError('Cannot specify params in responses, call register multiple times.') 179 | params = options.pop('params', {}) 180 | url = ImmutableFurl(uri, params=params) 181 | self.registry[(method, url)] = options.get('responses', options) 182 | 183 | def register_json_uri(self, method, uri, **options): 184 | body = json.dumps(options.pop('body', None)).encode('utf-8') 185 | headers = {'Content-Type': 'application/json'} 186 | headers.update(options.pop('headers', {})) 187 | self.register_uri(method, uri, body=body, headers=headers, **options) 188 | 189 | def activate(self): 190 | ClientSession._request, self.request = self.fake_request, ClientSession._request 191 | 192 | def deactivate(self): 193 | ClientSession._request, self.request = self.request, None 194 | 195 | def clear(self): 196 | self.calls = [] 197 | self.registry = {} 198 | 199 | def compare_call(self, first, second): 200 | for key, value in first.items(): 201 | if second.get(key) != value: 202 | return False 203 | return True 204 | 205 | def has_call(self, uri, check_params=True, **kwargs): 206 | """Check to see if the given uri was called. By default will verify that the query params 207 | match up. Setting ``check_params`` to `False` will strip params from the *called* uri, not 208 | the passed-in uri.""" 209 | kwargs['uri'] = ImmutableFurl(uri, params=kwargs.pop('params', None)) 210 | for call in self.calls: 211 | if not check_params: 212 | call = copy.deepcopy(call) 213 | call['uri'] = call['uri'].with_out_params() 214 | if self.compare_call(kwargs, call): 215 | return True 216 | return False 217 | 218 | 219 | sys.modules[__name__] = _AioHttPretty() 220 | -------------------------------------------------------------------------------- /tests/test_general.py: -------------------------------------------------------------------------------- 1 | import json 2 | import asyncio 3 | import unittest 4 | 5 | import pytest 6 | from aiohttp import ClientSession 7 | 8 | import aiohttpretty 9 | 10 | 11 | def async_test(f): 12 | def wrapper(*args, **kwargs): 13 | coro = asyncio.coroutine(f) 14 | future = coro(*args, **kwargs) 15 | loop = asyncio.get_event_loop() 16 | loop.run_until_complete(future) 17 | return wrapper 18 | 19 | 20 | class DummyAsyncStream(asyncio.StreamReader): 21 | 22 | def __init__(self, data): 23 | super().__init__() 24 | self.size = len(data) 25 | self.feed_data(data) 26 | self.feed_eof() 27 | 28 | 29 | class TestGeneral(unittest.TestCase): 30 | 31 | def tearDown(self): 32 | aiohttpretty.clear() 33 | 34 | @async_test 35 | async def test_fake_request(self): 36 | desired_response = b'example data' 37 | url = 'http://example.com/' 38 | 39 | aiohttpretty.register_uri('GET', url, body=desired_response) 40 | 41 | response = await aiohttpretty.fake_request('GET', url) 42 | data = await response.read() 43 | assert data == desired_response 44 | 45 | def test_register_uri(self): 46 | url = 'http://example.com/' 47 | desired_response = b'example data' 48 | 49 | aiohttpretty.register_uri('GET', url, body=desired_response) 50 | options = aiohttpretty.registry[('GET', 'http://example.com/')] 51 | assert options == {'body': b'example data'} 52 | 53 | def test_register_json_uri(self): 54 | url = 'http://example.com/' 55 | desired_response = {'test_key': 'test_value'} 56 | 57 | aiohttpretty.register_json_uri('GET', url, body=desired_response) 58 | options = aiohttpretty.registry[('GET', 'http://example.com/')] 59 | assert json.loads(options['body'].decode('utf-8')) == desired_response 60 | 61 | @async_test 62 | async def test_param_handling(self): 63 | url = 'http://example-params.com/?test=test' 64 | desired_error_msg = ( 65 | "No URLs matching GET http://example-params.com/?test=test with params {'test': 'test'}. " 66 | "Not making request. Go fix your test." 67 | ) 68 | try: 69 | await aiohttpretty.fake_request('GET', url) 70 | except Exception as exception: 71 | assert str(exception) == desired_error_msg 72 | 73 | @async_test 74 | async def test_params(self): 75 | desired_response = b'example data' 76 | url = 'http://example.com/' 77 | params = {'meow': 'quack', 'woof': 'beans'}; 78 | 79 | aiohttpretty.register_uri('GET', url, params=params, body=desired_response) 80 | 81 | response = await aiohttpretty.fake_request('GET', 82 | 'http://example.com/?meow=quack&woof=beans') 83 | data = await response.read() 84 | assert data == desired_response 85 | 86 | @async_test 87 | async def test_str_response_encoding(self): 88 | aiohttpretty.register_uri('GET', 89 | 'http://example.com/', 90 | body='example résumé data') 91 | response = await aiohttpretty.fake_request('GET', 92 | 'http://example.com/') 93 | data = await response.read() 94 | assert data == 'example résumé data'.encode('utf-8') 95 | 96 | @async_test 97 | async def test_has_call(self): 98 | aiohttpretty.register_uri('GET', 99 | 'http://example.com/', 100 | params={'alpha': '1', 'beta': None}, 101 | body='foo') 102 | response = await aiohttpretty.fake_request('GET', 103 | 'http://example.com/?alpha=1&beta=') 104 | assert await response.read() == b'foo' 105 | 106 | params_equivalent = [ 107 | 'http://example.com/?alpha=1&beta=', 108 | 'http://example.com/?beta=&alpha=1', 109 | ] 110 | for uri in params_equivalent: 111 | assert aiohttpretty.has_call(method='GET', uri=uri) 112 | 113 | params_different = [ 114 | 'http://example.com/', 115 | 'http://example.com/?alpha=2&beta=', 116 | # 'http://example.com/?alpha=1', # buggy atm 117 | 'http://example.com/?beta=', 118 | 'http://example.com/?alpha=1&beta=1', 119 | 'http://example.com/?alpha=&beta=', 120 | ] 121 | for uri in params_different: 122 | assert not aiohttpretty.has_call(method='GET', uri=uri) 123 | 124 | assert aiohttpretty.has_call(method='GET', uri='http://example.com/', 125 | params={'alpha': '1', 'beta': None}) 126 | assert aiohttpretty.has_call(method='GET', uri='http://example.com/', check_params=False) 127 | assert not aiohttpretty.has_call(method='POST', uri='http://example.com/?alpha=1&beta=') 128 | assert not aiohttpretty.has_call(method='GET', uri='http://otherexample.com/') 129 | 130 | def test_activate(self): 131 | orig_real_id = id(ClientSession._request) 132 | orig_fake_id = id(aiohttpretty.fake_request) 133 | 134 | assert aiohttpretty.request is None 135 | assert ClientSession._request != aiohttpretty.fake_request 136 | assert id(ClientSession._request) == orig_real_id 137 | assert id(ClientSession._request) != orig_fake_id 138 | 139 | aiohttpretty.activate() 140 | 141 | assert aiohttpretty.request is not None 142 | assert id(aiohttpretty.request) == orig_real_id 143 | 144 | assert ClientSession._request == aiohttpretty.fake_request 145 | assert id(ClientSession._request) != orig_real_id 146 | assert id(ClientSession._request) == orig_fake_id 147 | 148 | aiohttpretty.deactivate() 149 | 150 | assert aiohttpretty.request is None 151 | assert ClientSession._request != aiohttpretty.fake_request 152 | assert id(ClientSession._request) == orig_real_id 153 | assert id(ClientSession._request) != orig_fake_id 154 | 155 | @async_test 156 | async def test_multiple_responses(self): 157 | aiohttpretty.register_uri( 158 | 'GET', 159 | 'http://example.com/', 160 | responses=[ 161 | { 162 | 'status': 200, 163 | 'body': 'moo', 164 | }, 165 | { 166 | 'status': 200, 167 | 'body': 'quack', 168 | }, 169 | ], 170 | ) 171 | 172 | first_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') 173 | assert await first_resp.read() == b'moo' 174 | 175 | second_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') 176 | assert await second_resp.read() == b'quack' 177 | 178 | with pytest.raises(Exception) as exc: 179 | await aiohttpretty.fake_request('GET', 'http://example.com/') 180 | 181 | def test_no_params_in_responses(self): 182 | with pytest.raises(ValueError): 183 | aiohttpretty.register_uri( 184 | 'GET', 185 | 'http://example.com/', 186 | responses=[ 187 | { 188 | 'status': 200, 189 | 'body': 'moo', 190 | 'params': {'alpha': '1', 'beta': None} 191 | }, 192 | ], 193 | ) 194 | 195 | with pytest.raises(ValueError): 196 | aiohttpretty.register_uri( 197 | 'GET', 198 | 'http://example.com/', 199 | responses=[ 200 | { 201 | 'status': 200, 202 | 'body': 'woof', 203 | }, 204 | { 205 | 'status': 200, 206 | 'body': 'moo', 207 | 'params': {'alpha': '1', 'beta': None} 208 | }, 209 | ], 210 | ) 211 | 212 | @async_test 213 | async def test_headers_in_response(self): 214 | aiohttpretty.register_uri('GET', 'http://example.com/', 215 | headers={'X-Magic-Header': '1'}) 216 | 217 | first_resp = await aiohttpretty.fake_request('GET', 'http://example.com/') 218 | assert 'X-Magic-Header' in first_resp.headers 219 | 220 | @async_test 221 | async def test_async_streaming_body(self): 222 | stream = DummyAsyncStream(b'meow') 223 | aiohttpretty.register_uri('GET', 'http://example.com/', body=stream) 224 | 225 | resp = await aiohttpretty.fake_request('GET', 'http://example.com/') 226 | assert await resp.read() == b'meow' 227 | 228 | @async_test 229 | async def test_invalid_body(self): 230 | aiohttpretty.register_uri('GET', 'http://example.com/', body=1234) 231 | 232 | with pytest.raises(TypeError): 233 | await aiohttpretty.fake_request('GET', 'http://example.com/') 234 | 235 | @async_test 236 | async def test_passed_data_is_read(self): 237 | aiohttpretty.register_uri('GET', 'http://example.com/', body='woof') 238 | 239 | stream = DummyAsyncStream(b'meow') 240 | assert not stream.at_eof() 241 | 242 | resp = await aiohttpretty.fake_request('GET', 'http://example.com/', data=stream) 243 | 244 | assert stream.at_eof() 245 | assert await resp.read() == b'woof' 246 | 247 | @async_test 248 | async def test_aiohttp_request(self): 249 | aiohttpretty.register_uri('GET', 'http://example.com/', body=b'example data') 250 | 251 | aiohttpretty.activate() 252 | async with ClientSession() as session: 253 | async with session.get('http://example.com/') as response: 254 | assert await response.read() == b'example data' 255 | aiohttpretty.deactivate() 256 | --------------------------------------------------------------------------------