31

I don't understand for which purposes the decorator @pytest.mark.asyncio can be used.

I've tried to run the following code snippet with pytest and pytest-asyncio plugin installed and it failed, so I concluded that pytest collects test coroutines without the decorator. Why it exists so?

async def test_div():
    return 1 / 0
0

3 Answers 3

44

When your tests are marked with @pytest.mark.asyncio, they become coroutines, together with the keyword await in body

pytest will execute it as an asyncio task using the event loop provided by the event_loop fixture:

This code with decorator

@pytest.mark.asyncio
async def test_example(event_loop):
    do_stuff()    
    await asyncio.sleep(0.1, loop=event_loop)

is equal to writing this:

def test_example():
    loop = asyncio.new_event_loop()
    try:
        do_stuff()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(asyncio.sleep(0.1, loop=loop))
    finally:
        loop.close()

Note / Update

As of pytest-asyncio>=0.17 if you add asyncio_mode = auto to your config (pyproject.toml, setup.cfg or pytest.ini) there is no need for the marker, i.e. this behaviour is enabled for async tests automatically.

See https://pytest-asyncio.readthedocs.io/en/latest/reference/configuration.html

Sign up to request clarification or add additional context in comments.

Thank you! But what will differ if I omit the decorator?
@EgorOsokin: compare 2 versions, they are equivalents and using that decorator is easier version of using the event loop
12

Sławomir Lenart's answer is still correct, but note that as of pytest-asyncio>=0.17 if you add asyncio_mode = auto to your pyproject.toml or pytest.ini there is no need for the marker, i.e. this behaviour is enabled for async tests automatically.

See https://pytest-asyncio.readthedocs.io/en/latest/reference/configuration.html

You are correct but I think this should be part of the accepted answer, not a separate answer by itself, as it shouldn't become 'the right answer'. I'll try to merge it into the accepted one
4

I've tried to run the following code snippet with pytest and pytest-asyncio plugin installed and it "failed"

Actually the test is not going to get run. Even if you have pytest and pytest-asyncio installed, it will be skipped and you'll see a warning like this in output:

================================== warnings summary ====================================
tests.py::test_div
  /Users/.../ PytestUnhandledCoroutineWarning: async def functions are not natively supported and have been skipped.
  You need to install a suitable plugin for your async framework, for example:
    - anyio
    - pytest-asyncio
    - pytest-tornasync
    - pytest-trio
    - pytest-twisted
    warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
============================= 1 skipped, 1 warning in 0.02s =============================

As the documentation says:

A coroutine or async generator with this marker is treated as a test function by pytest.

So you have to mark your async functions.

The @pytest.mark.asyncio decorator also allows you to specify the scope so that some tests can share the same event loop, otherwise each test will run in it’s own event loop:

import asyncio
import pytest

@pytest.mark.asyncio(scope="class")
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

Comments

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.