pythontestingasync-awaitpytestteardown

How to make an async setup and teardown fixture in Pytest


I have a working basic example of a setup and teardown fixture in Pytest:


@pytest.fixture()
def setup_and_teardown():
    # Setup
    print('setup')
    
    # Yield some variable to the test
    yield 'v'
    
    # Teardown
    print('teardown')


async def test_me(setup_and_teardown):
    """Test successful experiment retrieval."""
    variable = db_data_setup_and_teardown

    # Call the method
    result = my_func_that_i_wrote_so_well(variable)

    # Assertions
    assert result == 'v2'

It works. But now I need async support. Both my setup and teardown steps are async. So I tried to do:

@pytest.fixture()
async def setup_and_teardown():
    # Setup
    print('setup')
    await my_setup_func()
    
    # Yield some variable to the test
    yield 'v'
    
    # Teardown
    print('teardown')
    await my_teardown_func()

but then variable = db_data_setup_and_teardown results in variable being an 'async_generator' object. If I try to use variable = await db_data_setup_and_teardown instead, then I just get the error

TypeError: object async_generator can't be used in 'await' expression

What is the correct way to convert this pytest fixture into something async?


Solution

  • It turns out that Pytest does NOT support async fixtures out of the box. It is necessary to install a third-party package (and pytest plugin) called pytest-asyncio.

    Then I needed to decorate the fixture with @pytest_asyncio.fixture() instead of @pytest.fixture(), and as @J_H had mentioned, also decorate the test function itself with @pytest.mark.asyncio. Those two changes alone fix the problem.

    So in summary the resulting code is:

    import pytest
    import pytest_asyncio
    
    
    @pytest_asyncio.fixture()
    async def setup_and_teardown():
        # Setup
        print('setup')
        await my_setup_func()
        
        # Yield some variable to the test
        yield 'v'
        
        # Teardown
        print('teardown')
        await my_teardown_func()
    
    
    @pytest.mark.asyncio
    async def test_me(setup_and_teardown):
        """Test successful experiment retrieval."""
        variable = db_data_setup_and_teardown
    
        # Call the method
        result = my_func_that_i_wrote_so_well(variable)
    
        # Assertions
        assert result == 'v2'
    

    and it works!