Multiple async unit tests fail, but running them one by one will pass
Asked Answered
M

1

7

I have two unit tests, if I run them one by one, they pass. If I run them at class level, one pass and the other one fails at response = await ac.post( with the error message: RuntimeError: Event loop is closed

@pytest.mark.asyncio
async def test_successful_register_saves_expiry_to_seven_days(self):
    async with AsyncClient(app=app, base_url="http://127.0.0.1") as ac:
        response = await ac.post(
            "/register/",
            headers={},
            json={
                "device_id": "u1",
                "device_type": DeviceType.IPHONE.value,
            },
        )
        query = device.select(whereclause=device.c.id == "u1")
        d = await db.fetch_one(query)
        assert d.expires_at == datetime.utcnow().replace(
            second=0, microsecond=0
        ) + timedelta(days=7)

@pytest.mark.asyncio
async def test_successful_register_saves_device_type(self):
    async with AsyncClient(app=app, base_url="http://127.0.0.1") as ac:
        response = await ac.post(
            "/register/",
            headers={},
            json={
                "device_id": "u1",
                "device_type": DeviceType.ANDROID.value,
            },
        )
        query = device.select(whereclause=device.c.id == "u1")
        d = await db.fetch_one(query)
        assert d.type == DeviceType.ANDROID.value

I have been trying for hours, what am I missing please?

Malebranche answered 4/2, 2021 at 21:50 Comment(2)
Please update your question to include a minimal, reproducible example (ideally, something we can copy to a local file and run pytest to reproduce the error). I put together a simple test with two async tests and it seems to run without a problem, leading me to wonder if there are other parts of your code that could be causing a problem.Rhodie
I have uploaded an example github.com/houmie/async-unittests. The test you have provided doesn't use the stack I had tagged, hence you couldn't reproduce it.Malebranche
M
19

UPDATE (>= 0.19.0)

Latest 0.19.0 of pytest-asyncio has become strict. You need now to change every @pytest.fixture in the conftest.py with @pytest_asyncio.fixture.

These things keep changing too often.


UPDATE (< 0.19.0)

It is true that @pytest.yield_fixture is deprecated. The proper way until version 0.19.0 is

@pytest.fixture(scope="session")
def event_loop(request):
    loop = asyncio.get_event_loop()
    yield loop
    loop.close()

Original Answer:

I have found the solution.

Create a file named conftest.py under tests

And insert the following:

@pytest.yield_fixture(scope="session")
def event_loop(request):
    """Create an instance of the default event loop for each test case."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

This will correctly end the loop after each test and allow multiple ones to be run.

Malebranche answered 16/2, 2021 at 13:22 Comment(9)
Thanks for the clear answer. I was searching for an hour, only to miss the necessary file name conftest.pyTripartite
You're welcome. Yeah, it's not straight forward.Malebranche
You are a day saver :)Journalese
Is there supposed to be some kind of implied change to the test case where the loop passed back from the fixture is used?Kennakennan
To this day, it works. Thanks! Tested on pytest==7.1.2 pytest-asyncio==0.18.3 uvicorn==0.17.6- I'm testing a fastapi app with fastapi==0.66.0Maharashtra
the fixture comment is misleading, it isn't creating an instance of the event loop for each test case, it's creating the event loop just once (for the session) and using it across all testsBanneret
On Pytest 7.1.2 with pytest-asyncio 0.18.3: PytestDeprecationWarning: @pytest.yield_fixture is deprecated.Responsory
hey, thank you for your comment and for the solution. Can you advice the similiar solution for Django async tests?Congratulation
You're welcome. I haven't worked with Django for a long time. Sorry.Malebranche

© 2022 - 2024 — McMap. All rights reserved.