Currently there are test utils in aiohttp.test_utils that helps to test aiohttp servers and clients.
But what about aiohttp.ClientSession ? How people could test that, mock it properly? Make it return developer-defined response without actually touching network?
That was a real pain for me until i wrote some helper functions for that.
@contextlib.contextmanager
def mock_session(response, session=None, mock_object=None):
"""
:param aiohttp.ClientSession session:
:param aiohttp.ClientResponse|list[aiohttp.ClientResponse] response:
"""
session = session or aiohttp.ClientSession()
request = session._request
session.mock = mock_object or mock.Mock()
if isinstance(response, (list, tuple)):
session.mock.side_effect = response
else:
session.mock.return_value = response
async def _request(*args, **kwargs):
return session.mock(*args, **kwargs)
session._request = _request
try:
yield session
finally:
session._request = request
delattr(session, 'mock')
def create_response(method, url, content, loop=None):
loop = loop or asyncio.get_event_loop()
response = aiohttp.ClientResponse(method.lower(), URL(url))
def side_effect(*args, **kwargs):
fut = loop.create_future()
if isinstance(content, str):
fut.set_result(content.encode())
else:
fut.set_result(content)
return fut
response.content = mock.Mock()
response.content.read.side_effect = side_effect
return response
Does aiohttp.test_utils need such functions? I could provide a PR for that.
@kxepal @asvetlov
For Home Assistant we have developed our own aiohttp.ClientSession mock: https://github.com/home-assistant/home-assistant/blob/dev/tests/test_util/aiohttp.py
We are down to contribute this back if there is interest. I checked for interest before on the aio-libs mailing list but got no reply.
@balloob just create PR
It right now only works with a subset of keywords of the aiohttp API for request. It is easy to extract for me and create a PR, but making it API complete and writing tests for it is not something I have time for right now.
@balloob that's fine.
There is example for fake server: https://github.com/aio-libs/aiohttp/blob/master/examples/fake_server.py
It's based on making custom resolver which redirects known DNS names to local web server.
I believe this technique is safe and easier to use than mock objects usage.
Maybe we need some scaffolds for fake server to test_utils.
found this, https://github.com/CircleUp/aresponses which works very well. The fake server is neat but when testing external interfaces really just want to add 1 line for the response vs 30-40 to create a fake server
@owaaa do you know that aresponses actually starts a fake server? :D
@asvetlov thats great if thats how it works, but if i had to create a whole fake server per the facebook example, I wouldn't have done it. The annotation is very useful if only mocking out ClientSession requests vs testing the aiohttp web server
It is a question of syntax sugar and scaffolding, not the evidence of fake server approach failure.
@asvetlov I agree, I didn't mean to apply the technical approach is wrong. Its just as a library consumer, I just want to write 2-3 lines to mock out a few things vs create a whole series of classes to mock out a few lines which isn't really internally maintainable. If anything is to be updated I'd suggest in the testing documentation would be nice to point to this type of direction, as I spent several hours evaluating options this morning, as approaches were non-obvious.
The "sugar" of these types of annotations is very similar to https://botocore.readthedocs.io/en/latest/reference/stubber.html, which goes a long way in cleaning up test code and the easier testing is the easier it is to get other developers on my team to write unit tests.
I will add some fresher code.
import asyncio
import contextlib
from json import dumps
from unittest import mock
import aiohttp
from yarl import URL
from aiohttp.helpers import TimerNoop
@contextlib.contextmanager
def mock_session(response, session=None, mock_object=None):
"""
:param aiohttp.ClientSession session:
:param aiohttp.ClientResponse|list[aiohttp.ClientResponse] response:
"""
session = session or aiohttp.ClientSession()
request = session._request
session.mock = mock_object or mock.Mock()
if isinstance(response, (list, tuple)):
session.mock.side_effect = response
else:
session.mock.return_value = response
async def _request(*args, **kwargs):
return session.mock(*args, **kwargs)
session._request = _request
try:
yield session
finally:
session._request = request
delattr(session, 'mock')
def create_response(
method,
url,
session,
loop=None,
content=None,
json=None,
status=None):
encode = "utf-8"
loop = loop or asyncio.get_event_loop()
response = aiohttp.ClientResponse(
method.lower(),
URL(url),
request_info=mock.Mock(),
writer=mock.Mock(),
continue100=None,
timer=TimerNoop(),
traces=[],
loop=loop,
session=session
)
cnt = content
if status is not None:
response.status = int(status)
response._headers = {}
if json is not None:
response._headers.update({
'Content-Type':
'application/json;charset=' + encode})
cnt = dumps(json)
def side_effect(*args, **kwargs):
fut = loop.create_future()
if isinstance(cnt, str):
fut.set_result(cnt.encode(encode))
else:
fut.set_result(cnt)
return fut
response.content = mock.Mock()
response.content.read.side_effect = side_effect
return response
Too much mocking from my taste
+1 for aresponses, it is a joy to use.
Most helpful comment
For Home Assistant we have developed our own
aiohttp.ClientSessionmock: https://github.com/home-assistant/home-assistant/blob/dev/tests/test_util/aiohttp.pyWe are down to contribute this back if there is interest. I checked for interest before on the aio-libs mailing list but got no reply.