6

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?

Houman
  • 64,245
  • 87
  • 278
  • 460
  • 1
    Please update your question to include a [minimal, reproducible example](https://stackoverflow.com/help/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](https://gist.github.com/larsks/9c5bbc05a26dd200c28e39273e1e9ea9) 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. – larsks Feb 04 '21 at 22:52
  • I have uploaded an example https://github.com/houmie/async-unittests. The test you have provided doesn't use the stack I had tagged, hence you couldn't reproduce it. – Houman Feb 05 '21 at 09:04

1 Answers1

18

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.

Houman
  • 64,245
  • 87
  • 278
  • 460
  • Thanks for the clear answer. I was searching for an hour, only to miss the necessary file name `conftest.py` – Arshad Ansari Nov 25 '21 at 09:45
  • You're welcome. Yeah, it's not straight forward. – Houman Nov 25 '21 at 12:15
  • 1
    You are a day saver :) – RKO Jan 07 '22 at 18:30
  • Is there supposed to be some kind of implied change to the test case where the loop passed back from the fixture is used? – LeanMan May 03 '22 at 14:14
  • 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.0` – lsabi May 03 '22 at 20:52
  • 2
    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 tests – Marcelo May 06 '22 at 13:57
  • On Pytest 7.1.2 with pytest-asyncio 0.18.3: `PytestDeprecationWarning: @pytest.yield_fixture is deprecated.` – gertvdijk Jun 10 '22 at 22:34
  • hey, thank you for your comment and for the solution. Can you advice the similiar solution for Django async tests? – Q-bart Aug 03 '23 at 04:21
  • You're welcome. I haven't worked with Django for a long time. Sorry. – Houman Aug 03 '23 at 12:36