diff --git a/README.rst b/README.rst index 83a969d7..b59bfa93 100644 --- a/README.rst +++ b/README.rst @@ -114,7 +114,7 @@ Async fixtures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be coroutines or asynchronous generators. -.. code-block:: python +.. code-block:: python3 @pytest.fixture async def async_gen_fixture(): @@ -130,6 +130,20 @@ to redefine the ``event_loop`` fixture to have the same or broader scope. Async fixtures need the event loop, and so must have the same or narrower scope than the ``event_loop`` fixture. +If you want to do this with Python 3.5, the ``yield`` statement must be replaced with ``await yield_()`` and the coroutine +function must be decorated with ``@async_generator``, like so: + +.. code-block:: python3 + + from async_generator import yield_, async_generator + + @pytest.fixture + @async_generator + async def async_gen_fixture(): + await asyncio.sleep(0.1) + await yield_('a value') + + Markers ------- diff --git a/pytest_asyncio/plugin.py b/pytest_asyncio/plugin.py index 681cb161..309bdf76 100644 --- a/pytest_asyncio/plugin.py +++ b/pytest_asyncio/plugin.py @@ -3,25 +3,22 @@ import contextlib import inspect import socket -import sys from concurrent.futures import ProcessPoolExecutor import pytest from _pytest.python import transfer_markers +try: + from async_generator import isasyncgenfunction +except ImportError: + from inspect import isasyncgenfunction + def _is_coroutine(obj): """Check to see if an object is really an asyncio coroutine.""" return asyncio.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj) -if sys.version_info[:2] < (3, 6): - def isasyncgenfunction(_): - return False -else: - from inspect import isasyncgenfunction - - def pytest_configure(config): """Inject documentation.""" config.addinivalue_line("markers", diff --git a/setup.py b/setup.py index bdea43a0..6cdade78 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,10 @@ def find_version(*file_paths): install_requires=[ 'pytest >= 3.0.6', ], + extras_require={ + ':python_version == "3.5"': 'async_generator >= 1.3' + }, entry_points={ 'pytest11': ['asyncio = pytest_asyncio.plugin'], - }, + } ) diff --git a/test_requirements.txt b/test_requirements.txt index 8ae78b7a..e2f0b366 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,2 +1,3 @@ coverage==4.1 -tox==2.5.0 \ No newline at end of file +tox==2.5.0 +async_generator==1.8 \ No newline at end of file diff --git a/tests/async_fixtures/test_async_gen_fixtures_35.py b/tests/async_fixtures/test_async_gen_fixtures_35.py new file mode 100644 index 00000000..3b552fab --- /dev/null +++ b/tests/async_fixtures/test_async_gen_fixtures_35.py @@ -0,0 +1,40 @@ +import unittest.mock + +import pytest +from async_generator import yield_, async_generator + +START = object() +END = object() +RETVAL = object() + + +@pytest.fixture(scope='module') +def mock(): + return unittest.mock.Mock(return_value=RETVAL) + + +@pytest.fixture +@async_generator +async def async_gen_fixture(mock): + try: + await yield_(mock(START)) + except Exception as e: + mock(e) + else: + mock(END) + + +@pytest.mark.asyncio +async def test_async_gen_fixture(async_gen_fixture, mock): + assert mock.called + assert mock.call_args_list[-1] == unittest.mock.call(START) + assert async_gen_fixture is RETVAL + + +@pytest.mark.asyncio +async def test_async_gen_fixture_finalized(mock): + try: + assert mock.called + assert mock.call_args_list[-1] == unittest.mock.call(END) + finally: + mock.reset_mock()