0

I'm testing my Python software using Pytest. I have to call many async function and sometimes my tests pass even when I forgot to write the await keyword.

I would like my test to automatically fail if I call an async function without await.

I was thinking about a decorator to add at the top of my tests, something like

async def func():
    return 42

@checkawait
async def test_await():
    func() # <-- forgot to await

I would like this test to fail with the decorator, because func is an async function that was never awaited. (I know this is not a proper test, since I'm not testing anything. It's just an example).

Without the decorator test_await passes.

I really don't know what to do.

I asked chatGPT which told me to use this

def checkawait(test):
    @functools.wraps(test)
    async def wrapper(*args, **kwargs):
        coros = []
        res = test(*args, **kwargs)
        if asyncio.iscoroutine(res):
            coros.append(res)
        while coros:
            done, coros = await asyncio.wait(coros, timeout=0.1)
            if not done:
                raise Exception("Not all coroutines have completed")
        return res
    return wrapper

which, of course, is not working.

Does anyone have any ideas? Is it even possible to do so? Thanks

CrazyChucky
  • 3,263
  • 4
  • 11
  • 25
chc
  • 498
  • 1
  • 4
  • 18
  • I currently get a RunTime warning when I run an async function without awaiting it... (can't repro) – 12944qwerty Feb 23 '23 at 16:36
  • Please [edit](https://stackoverflow.com/posts/75547485/edit) your question to include a [minimum reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) with test data demonstrating your problem. – Woodford Feb 23 '23 at 16:45
  • @Woodford I edited my example to be reproducible – chc Feb 23 '23 at 16:57
  • When I execute `await test_await()` I get a `RuntimeWarning` as well – Woodford Feb 23 '23 at 17:00
  • What version are you on? – 12944qwerty Feb 23 '23 at 17:56
  • @Woodford It actually gives me `RuntimeWarning: coroutine 'func' was never awaited`, but the test still passes. I tried `pytest test.py -W error::RuntimeWarning` (like this https://stackoverflow.com/questions/54831333/how-do-i-make-pytest-crash-on-a-warning) but the warning turned into `PytestUnraisableExceptionWarning`, and the test still passes. – chc Feb 23 '23 at 20:48
  • @12944qwerty `Python 3.10.5`, `pytest-7.2.1`, `pluggy-1.0.0`, `asyncio-0.19.0`, `pretty-1.0.1`, `mock-3.10.0`, `subprocess-1.5.0`, `docker-1.0.1`, `anyio-3.6.2` – chc Feb 23 '23 at 20:49
  • Just edited the answer with a possible solution – chc Feb 23 '23 at 22:01
  • 2
    Instead of editing the question with an answer, you should actually answer the question and then accept it. – 12944qwerty Feb 23 '23 at 22:49
  • 1
    I rolled back to the revision before the answer was added. – CrazyChucky Feb 23 '23 at 23:04

1 Answers1

0

Not awaited coroutines generate RuntimeWarnings. Running pytest as pytest test.py -W error::RuntimeWarning, we get a PytestUnraisableExceptionWarning and the test still pass.

I thought of this solution

import functools
import warnings

def checkawait(my_test):
    @functools.wraps(my_test)
    async def wrapper(*args, **kwargs):
        with pytest.warns(RuntimeWarning) as record:
            await my_test(*args, **kwargs)
            captured_warnings = record.list
            if not captured_warnings:
                warnings.warn("Harmless warning", RuntimeWarning)
        for warning in captured_warnings:
            if "never awaited" in warning.message.args[0]:
                raise Exception(warning.message.args[0])
    return wrapper

Used as a decorator, checkawait fails the first test but passes the second one:

async def func():
    return 42

@checkawait
async def test_fail():
    func()

@checkawait
async def test_pass():
    await func()
chc
  • 498
  • 1
  • 4
  • 18