-
Notifications
You must be signed in to change notification settings - Fork 41
Open
Description
Setup
I use Python 3.7.8 and the following setup:
# requirements.txt
asynctest==0.13.0
pytest==5.4.3
pytest-asyncio==0.14.0
and the test file
# test.py
import pytest
from asynctest import mock
async def hello():
return 'HELLO'
@pytest.mark.asyncio
@mock.patch('test.hello')
class TestFetch:
async def test_will_pass(self, mock_hello):
mock_hello.return_value = 'OK'
assert await hello() == 'OK'
@pytest.mark.parametrize('x',[1, 2])
async def test_will_fail_on_second_and_further_params(self, mock_hello, x):
mock_hello.return_value = 'OK'
assert await hello() == 'OK'
The issue
After I run the tests:
py.test test.py
I get the following stacktrace:
____________________________________________________________________ TestFetch.test_will_fail_on_second_and_further_params[2] _____________________________________________________________________
self = <[AttributeError("'_PatchedGenerator' object has no attribute 'generator'") raised in repr()] _PatchedGenerator object at 0x10cb4a290>
def __next__(self):
try:
with self._limited_patchings_stack():
> return self.gen.send(None)
E StopIteration
venv/lib/python3.7/site-packages/asynctest/mock.py:1041: StopIteration
During handling of the above exception, another exception occurred:
kwargs = {'x': 2}, coro = <coroutine object TestFetch.test_will_fail_on_second_and_further_params at 0x10caff170>
task = <Task finished coro=<TestFetch.test_will_fail_on_second_and_further_params() done, defined at /Users/aikikode/tmp/test...hon3.7/site-packages/asynctest/mock.py:113> exception=AttributeError("'_patch' object has no attribute '_exit_stack'")>
@functools.wraps(func)
def inner(**kwargs):
coro = func(**kwargs)
if coro is not None:
task = asyncio.ensure_future(coro, loop=_loop)
try:
> _loop.run_until_complete(task)
venv/lib/python3.7/site-packages/pytest_asyncio/plugin.py:179:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.pyenv/versions/3.7.8/lib/python3.7/asyncio/base_events.py:587: in run_until_complete
return future.result()
venv/lib/python3.7/site-packages/asynctest/mock.py:115: in wrapper
return await coroutine(*args, **kwargs)
venv/lib/python3.7/site-packages/asynctest/mock.py:1044: in __next__
self._stop_global_patchings()
venv/lib/python3.7/site-packages/asynctest/mock.py:1033: in _stop_global_patchings
patching.stop()
../../.pyenv/versions/3.7.8/lib/python3.7/unittest/mock.py:1455: in stop
return self.__exit__(None, None, None)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <asynctest.mock._patch object at 0x10caf5e10>, exc_info = (None, None, None)
def __exit__(self, *exc_info):
"""Undo the patch."""
if not _is_started(self):
raise RuntimeError('stop called on unstarted patcher')
if self.is_local and self.temp_original is not DEFAULT:
setattr(self.target, self.attribute, self.temp_original)
else:
delattr(self.target, self.attribute)
if not self.create and (not hasattr(self.target, self.attribute) or
self.attribute in ('__doc__', '__module__',
'__defaults__', '__annotations__',
'__kwdefaults__')):
# needed for proxy objects like django settings
setattr(self.target, self.attribute, self.temp_original)
del self.temp_original
del self.is_local
del self.target
> exit_stack = self._exit_stack
E AttributeError: '_patch' object has no attribute '_exit_stack'
../../.pyenv/versions/3.7.8/lib/python3.7/unittest/mock.py:1435: AttributeError
Investigation
I did some investigation for the possible cause of this issue:
_exit_stackwas added in Python 3.7.8: python/cpython@4057e8f#diff-ff75b1b83c21770847ade91fa5bb2525L1291 It's set upon patch__enter()__and deleted in__exit()__.asynctestusesmock_to_reuseto reuse the same mock object for a coroutine: https://github.com/Martiusweb/asynctest/blob/v0.13.0/asynctest/mock.py#L1088 - if we reuse the coroutine, then patch__enter()__method is not called and_exit_stackattribute is not set.- when used in combination with
pytest.mark.parametrizeit leads to mock__enter()__being called only once per test case (regardless of number of "subcases" created with parametrisation), but the patch__exit()__is called after each of the parametrisation "subtests", so only the first "subtest" passes and all other fail.
belegnar, seedofjoy, MikhailLychagin, blankdots, galeNightIn and 1 more
Metadata
Metadata
Assignees
Labels
No labels