ca9163d9
ca9163d9

Reputation: 29159

How to test async function using pytest?

@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
=============

Upvotes: 49

Views: 62739

Answers (3)

João Fé
João Fé

Reputation: 385

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.

EDIT: You can ignore "functional core, imperative shell" in relation to tests, although that is still useful advice for design in general IMO. For tests you just have to abstract the effect type. Oh is this Python? Just use a better language, like Haskell or Scala.

Upvotes: 2

SystemSigma_
SystemSigma_

Reputation: 1435

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.

Upvotes: 56

VPfB
VPfB

Reputation: 17237

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

Upvotes: 62

Related Questions