diff --git a/docs/source/reference/decorators/fixture_strict_mode_example.py b/docs/source/reference/decorators/fixture_strict_mode_example.py new file mode 100644 index 00000000..6442c103 --- /dev/null +++ b/docs/source/reference/decorators/fixture_strict_mode_example.py @@ -0,0 +1,14 @@ +import asyncio + +import pytest_asyncio + + +@pytest_asyncio.fixture +async def async_gen_fixture(): + await asyncio.sleep(0.1) + yield "a value" + + +@pytest_asyncio.fixture(scope="module") +async def async_fixture(): + return await asyncio.sleep(0.1) diff --git a/docs/source/reference/decorators.rst b/docs/source/reference/decorators/index.rst similarity index 66% rename from docs/source/reference/decorators.rst rename to docs/source/reference/decorators/index.rst index 977ed6b8..5c96cf4b 100644 --- a/docs/source/reference/decorators.rst +++ b/docs/source/reference/decorators/index.rst @@ -3,20 +3,8 @@ Decorators ========== Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be decorated with ``@pytest_asyncio.fixture``. -.. code-block:: python3 - - import pytest_asyncio - - - @pytest_asyncio.fixture - async def async_gen_fixture(): - await asyncio.sleep(0.1) - yield "a value" - - - @pytest_asyncio.fixture(scope="module") - async def async_fixture(): - return await asyncio.sleep(0.1) +.. include:: fixture_strict_mode_example.py + :code: python All scopes are supported, but if you use a non-function scope you will need to redefine the ``event_loop`` fixture to have the same or broader scope. diff --git a/docs/source/reference/fixtures/event_loop_example.py b/docs/source/reference/fixtures/event_loop_example.py new file mode 100644 index 00000000..b5a82b62 --- /dev/null +++ b/docs/source/reference/fixtures/event_loop_example.py @@ -0,0 +1,5 @@ +import asyncio + + +def test_event_loop_fixture(event_loop): + event_loop.run_until_complete(asyncio.sleep(0)) diff --git a/docs/source/reference/fixtures.rst b/docs/source/reference/fixtures/index.rst similarity index 90% rename from docs/source/reference/fixtures.rst rename to docs/source/reference/fixtures/index.rst index d5032ba9..98fe5382 100644 --- a/docs/source/reference/fixtures.rst +++ b/docs/source/reference/fixtures/index.rst @@ -9,12 +9,8 @@ is available as the return value of this fixture or via `asyncio.get_running_loo The event loop is closed when the fixture scope ends. The fixture scope defaults to ``function`` scope. -.. code-block:: python - - def test_http_client(event_loop): - url = "http://httpbin.org/get" - resp = event_loop.run_until_complete(http_client(url)) - assert b"HTTP/1.1 200 OK" in resp +.. include:: event_loop_example.py + :code: python Note that, when using the ``event_loop`` fixture, you need to interact with the event loop using methods like ``event_loop.run_until_complete``. If you want to *await* code inside your test function, you need to write a coroutine and use it as a test function. The `asyncio <#pytest-mark-asyncio>`__ marker is used to mark coroutines that should be treated as test functions. diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index c07d0e19..5fdc2724 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -6,9 +6,9 @@ Reference :hidden: configuration - fixtures - markers - decorators + fixtures/index + markers/index + decorators/index changelog This section of the documentation provides descriptions of the individual parts provided by pytest-asyncio. diff --git a/docs/source/reference/markers.rst b/docs/source/reference/markers.rst deleted file mode 100644 index 68d5efd3..00000000 --- a/docs/source/reference/markers.rst +++ /dev/null @@ -1,180 +0,0 @@ -======= -Markers -======= - -``pytest.mark.asyncio`` -======================= -A coroutine or async generator with this marker will be treated as a test function by pytest. The marked function will be executed as an -asyncio task in the event loop provided by the ``event_loop`` fixture. - -In order to make your test code a little more concise, the pytest |pytestmark|_ -feature can be used to mark entire modules or classes with this marker. -Only test coroutines will be affected (by default, coroutines prefixed by -``test_``), so, for example, fixtures are safe to define. - -.. code-block:: python - - import asyncio - - import pytest - - # All test coroutines will be treated as marked. - pytestmark = pytest.mark.asyncio - - - async def test_example(event_loop): - """No marker!""" - await asyncio.sleep(0, loop=event_loop) - -In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted, the marker is added -automatically to *async* test functions. - - -``pytest.mark.asyncio_event_loop`` -================================== -Test classes or modules with this mark provide a class-scoped or module-scoped asyncio event loop. - -This functionality is orthogonal to the `asyncio` mark. -That means the presence of this mark does not imply that async test functions inside the class or module are collected by pytest-asyncio. -The collection happens automatically in `auto` mode. -However, if you're using strict mode, you still have to apply the `asyncio` mark to your async test functions. - -The following code example uses the `asyncio_event_loop` mark to provide a shared event loop for all tests in `TestClassScopedLoop`: - -.. code-block:: python - - import asyncio - - import pytest - - - @pytest.mark.asyncio_event_loop - class TestClassScopedLoop: - loop: asyncio.AbstractEventLoop - - @pytest.mark.asyncio - async def test_remember_loop(self): - TestClassScopedLoop.loop = asyncio.get_running_loop() - - @pytest.mark.asyncio - async def test_this_runs_in_same_loop(self): - assert asyncio.get_running_loop() is TestClassScopedLoop.loop - -In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted: - -.. code-block:: python - - import asyncio - - import pytest - - - @pytest.mark.asyncio_event_loop - class TestClassScopedLoop: - loop: asyncio.AbstractEventLoop - - async def test_remember_loop(self): - TestClassScopedLoop.loop = asyncio.get_running_loop() - - async def test_this_runs_in_same_loop(self): - assert asyncio.get_running_loop() is TestClassScopedLoop.loop - -Similarly, a module-scoped loop is provided when adding the `asyncio_event_loop` mark to the module: - -.. code-block:: python - - import asyncio - - import pytest - - pytestmark = pytest.mark.asyncio_event_loop - - loop: asyncio.AbstractEventLoop - - - async def test_remember_loop(): - global loop - loop = asyncio.get_running_loop() - - - async def test_this_runs_in_same_loop(): - global loop - assert asyncio.get_running_loop() is loop - - - class TestClassA: - async def test_this_runs_in_same_loop(self): - global loop - assert asyncio.get_running_loop() is loop - -The `asyncio_event_loop` mark supports an optional `policy` keyword argument to set the asyncio event loop policy. - -.. code-block:: python - - import asyncio - - import pytest - - - class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): - pass - - - @pytest.mark.asyncio_event_loop(policy=CustomEventLoopPolicy()) - class TestUsesCustomEventLoopPolicy: - @pytest.mark.asyncio - async def test_uses_custom_event_loop_policy(self): - assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) - - -The ``policy`` keyword argument may also take an iterable of event loop policies. This causes tests under by the `asyncio_event_loop` mark to be parametrized with different policies: - -.. code-block:: python - - import asyncio - - import pytest - - import pytest_asyncio - - - @pytest.mark.asyncio_event_loop( - policy=[ - asyncio.DefaultEventLoopPolicy(), - uvloop.EventLoopPolicy(), - ] - ) - class TestWithDifferentLoopPolicies: - @pytest.mark.asyncio - async def test_parametrized_loop(self): - pass - - -If no explicit policy is provided, the mark will use the loop policy returned by ``asyncio.get_event_loop_policy()``. - -Fixtures and tests sharing the same `asyncio_event_loop` mark are executed in the same event loop: - -.. code-block:: python - - import asyncio - - import pytest - - import pytest_asyncio - - - @pytest.mark.asyncio_event_loop - class TestClassScopedLoop: - loop: asyncio.AbstractEventLoop - - @pytest_asyncio.fixture - async def my_fixture(self): - TestClassScopedLoop.loop = asyncio.get_running_loop() - - @pytest.mark.asyncio - async def test_runs_is_same_loop_as_fixture(self, my_fixture): - assert asyncio.get_running_loop() is TestClassScopedLoop.loop - - -.. |pytestmark| replace:: ``pytestmark`` -.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules diff --git a/docs/source/reference/markers/class_scoped_loop_auto_mode_example.py b/docs/source/reference/markers/class_scoped_loop_auto_mode_example.py new file mode 100644 index 00000000..a839e571 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_auto_mode_example.py @@ -0,0 +1,14 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio_event_loop +class TestClassScopedLoop: + loop: asyncio.AbstractEventLoop + + async def test_remember_loop(self): + TestClassScopedLoop.loop = asyncio.get_running_loop() + + async def test_this_runs_in_same_loop(self): + assert asyncio.get_running_loop() is TestClassScopedLoop.loop diff --git a/docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py new file mode 100644 index 00000000..85ccc3a1 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py @@ -0,0 +1,15 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio_event_loop( + policy=[ + asyncio.DefaultEventLoopPolicy(), + asyncio.DefaultEventLoopPolicy(), + ] +) +class TestWithDifferentLoopPolicies: + @pytest.mark.asyncio + async def test_parametrized_loop(self): + pass diff --git a/docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py new file mode 100644 index 00000000..b4525ca4 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py @@ -0,0 +1,14 @@ +import asyncio + +import pytest + + +class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): + pass + + +@pytest.mark.asyncio_event_loop(policy=CustomEventLoopPolicy()) +class TestUsesCustomEventLoopPolicy: + @pytest.mark.asyncio + async def test_uses_custom_event_loop_policy(self): + assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) diff --git a/docs/source/reference/markers/class_scoped_loop_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_strict_mode_example.py new file mode 100644 index 00000000..c33b34b8 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_strict_mode_example.py @@ -0,0 +1,16 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio_event_loop +class TestClassScopedLoop: + loop: asyncio.AbstractEventLoop + + @pytest.mark.asyncio + async def test_remember_loop(self): + TestClassScopedLoop.loop = asyncio.get_running_loop() + + @pytest.mark.asyncio + async def test_this_runs_in_same_loop(self): + assert asyncio.get_running_loop() is TestClassScopedLoop.loop diff --git a/docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py new file mode 100644 index 00000000..c70a4bc6 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py @@ -0,0 +1,18 @@ +import asyncio + +import pytest + +import pytest_asyncio + + +@pytest.mark.asyncio_event_loop +class TestClassScopedLoop: + loop: asyncio.AbstractEventLoop + + @pytest_asyncio.fixture + async def my_fixture(self): + TestClassScopedLoop.loop = asyncio.get_running_loop() + + @pytest.mark.asyncio + async def test_runs_is_same_loop_as_fixture(self, my_fixture): + assert asyncio.get_running_loop() is TestClassScopedLoop.loop diff --git a/docs/source/reference/markers/index.rst b/docs/source/reference/markers/index.rst new file mode 100644 index 00000000..6c3e5253 --- /dev/null +++ b/docs/source/reference/markers/index.rst @@ -0,0 +1,65 @@ +======= +Markers +======= + +``pytest.mark.asyncio`` +======================= +A coroutine or async generator with this marker will be treated as a test function by pytest. The marked function will be executed as an +asyncio task in the event loop provided by the ``event_loop`` fixture. + +In order to make your test code a little more concise, the pytest |pytestmark|_ +feature can be used to mark entire modules or classes with this marker. +Only test coroutines will be affected (by default, coroutines prefixed by +``test_``), so, for example, fixtures are safe to define. + +.. include:: pytestmark_asyncio_strict_mode_example.py + :code: python + +In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted, the marker is added +automatically to *async* test functions. + + +``pytest.mark.asyncio_event_loop`` +================================== +Test classes or modules with this mark provide a class-scoped or module-scoped asyncio event loop. + +This functionality is orthogonal to the `asyncio` mark. +That means the presence of this mark does not imply that async test functions inside the class or module are collected by pytest-asyncio. +The collection happens automatically in `auto` mode. +However, if you're using strict mode, you still have to apply the `asyncio` mark to your async test functions. + +The following code example uses the `asyncio_event_loop` mark to provide a shared event loop for all tests in `TestClassScopedLoop`: + +.. include:: class_scoped_loop_strict_mode_example.py + :code: python + +In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted: + +.. include:: class_scoped_loop_auto_mode_example.py + :code: python + +Similarly, a module-scoped loop is provided when adding the `asyncio_event_loop` mark to the module: + +.. include:: module_scoped_loop_auto_mode_example.py + :code: python + +The `asyncio_event_loop` mark supports an optional `policy` keyword argument to set the asyncio event loop policy. + +.. include:: class_scoped_loop_custom_policy_strict_mode_example.py + :code: python + + +The ``policy`` keyword argument may also take an iterable of event loop policies. This causes tests under by the `asyncio_event_loop` mark to be parametrized with different policies: + +.. include:: class_scoped_loop_custom_policies_strict_mode_example.py + :code: python + +If no explicit policy is provided, the mark will use the loop policy returned by ``asyncio.get_event_loop_policy()``. + +Fixtures and tests sharing the same `asyncio_event_loop` mark are executed in the same event loop: + +.. include:: class_scoped_loop_with_fixture_strict_mode_example.py + :code: python + +.. |pytestmark| replace:: ``pytestmark`` +.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules diff --git a/docs/source/reference/markers/module_scoped_loop_auto_mode_example.py b/docs/source/reference/markers/module_scoped_loop_auto_mode_example.py new file mode 100644 index 00000000..e38bdeff --- /dev/null +++ b/docs/source/reference/markers/module_scoped_loop_auto_mode_example.py @@ -0,0 +1,23 @@ +import asyncio + +import pytest + +pytestmark = pytest.mark.asyncio_event_loop + +loop: asyncio.AbstractEventLoop + + +async def test_remember_loop(): + global loop + loop = asyncio.get_running_loop() + + +async def test_this_runs_in_same_loop(): + global loop + assert asyncio.get_running_loop() is loop + + +class TestClassA: + async def test_this_runs_in_same_loop(self): + global loop + assert asyncio.get_running_loop() is loop diff --git a/docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py b/docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py new file mode 100644 index 00000000..f1465728 --- /dev/null +++ b/docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py @@ -0,0 +1,11 @@ +import asyncio + +import pytest + +# All test coroutines will be treated as marked. +pytestmark = pytest.mark.asyncio + + +async def test_example(): + """No marker!""" + await asyncio.sleep(0)