23
@pytest.fixture
def d_service():
    c = DService()
    return c

# @pytest.mark.asyncio  # tried it too
async def test_get_file_list(d_service):
    files = await d_service.get_file_list('')
    print(files)

However, it got the following error?

collected 0 items / 1 errors

=================================== ERRORS ====================================
________________ ERROR collecting tests/e2e_tests/test_d.py _________________
..\..\..\..\..\anaconda3\lib\site-packages\pluggy\__init__.py:617: in __call__
    return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
..\..\..\..\..\anaconda3\lib\site-packages\pluggy\__init__.py:222: in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
..\..\..\..\..\anaconda3\lib\site-packages\pluggy\__init__.py:216: in 
    firstresult=hook.spec_opts.get('firstresult'),
..\..\..\..\..\anaconda3\lib\site-packages\_pytest\python.py:171: in pytest_pycollect_makeitem
    res = outcome.get_result()
..\..\..\..\..\anaconda3\lib\site-packages\anyio\pytest_plugin.py:98: in pytest_pycollect_makeitem
    marker = collector.get_closest_marker('anyio')
E   AttributeError: 'Module' object has no attribute 'get_closest_marker'
!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!
=========================== 1 error in 2.53 seconds ===========================

I installed the following package. The error is gone but the test is skipped.

pip install pytest-asyncio  
(base) PS>pytest -s tests\e2e_tests\test_d.py
================================================================================================================== test session starts ===================================================================================================================
platform win32 -- Python 3.6.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: C:\Users\X01324908\source\rds\research_data_science\sftp\file_handler
plugins: anyio-3.3.4, asyncio-0.16.0
collected 1 item

tests\e2e_tests\test_d.py s

==================================================================================================================== warnings summary ====================================================================================================================
tests/e2e_tests/test_d.py::test_get_file_list
  c:\users\x01324908\anaconda3\lib\site-packages\_pytest\python.py:172: 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/warnings.html
=============
funnydman
  • 9,083
  • 4
  • 40
  • 55
ca9163d9
  • 27,283
  • 64
  • 210
  • 413
  • What is your pytest version? – Guy Nov 18 '21 at 07:02
  • Updated the question. The version is 6.2.5 – ca9163d9 Nov 18 '21 at 07:06
  • Regarding the collection error - there must be something fishy in your project layout. Can you add a [mcve]? Other than that: if you want to run tests with `anyio`/`asyncio`, you have to mark them with `pytest.mark.anyio`/`pytest.mark.asyncio`. Otherwise, the async tests will be skipped and the (bit misleading) warning will be displayed - this is the correct behaviour. – hoefling Nov 18 '21 at 08:26

3 Answers3

34

This works for me, please try:

import asyncio
import pytest

pytest_plugins = ('pytest_asyncio',)

@pytest.mark.asyncio
async def test_simple():
    await asyncio.sleep(0.5)

Output of pytest -v confirms it passes:

collected 1 item
test_async.py::test_simple PASSED

And I have installed:

pytest                        6.2.5
pytest-asyncio                0.16.0
# anyio not installed
VPfB
  • 14,927
  • 6
  • 41
  • 75
  • 3
    The test would be more convincing if it also showed a successful assertion and a failing one after the `await` operation. – Thomas Nov 18 '21 at 08:09
  • Thanks, I added `pytest_plugins = ('pytest_asyncio',)` and it shows passed instead of skipped. – ca9163d9 Nov 18 '21 at 13:16
  • I hope my answer helps, but I do not understand why should I convinvce anybody here. I wrote that it works for me and what I mean is that I have somewhere between 100 and 200 such unit tests that I ran many many times. – VPfB Sep 03 '22 at 14:42
25

You can make pytest-asyncio automatically detect async def test_* functions as proper test by adding a file in your tests/ folder called pytest.ini with the following content:

# pytest.ini
[pytest]
asyncio_mode=auto

This way you don't even need to decorate/mark your async def tests, as explained in the Modes section of the documentation.

SystemSigma_
  • 1,059
  • 4
  • 18
  • You can also configure pytest in pyproject.toml under the `[tool.pytest.ini_options]` header, per pytest docs https://docs.pytest.org/en/7.1.x/reference/customize.html – four43 Aug 16 '23 at 20:08
2

Homemade solution

A decorator that runs the test coroutine in the event loop:

import asyncio
import inspect


def asyncio_run(async_func):

    def wrapper(*args, **kwargs):
        return asyncio.run(async_func(*args, **kwargs))
    
    wrapper.__signature__ = inspect.signature(async_func)  # without this, fixtures are not injected

    return wrapper


@asyncio_run
async def test_get_file_list(d_service):
    files = await d_service.get_file_list('')
    print(files)

Personal Advice

Avoid async tests as much as possible. Try to make most of your code pure — then most of your tests will be fast and reliable/deterministic (which might not be the case with asynchronous code). Search for "functional core, imperative shell" for more.

João Fé
  • 331
  • 2
  • 8