Python Friday #239: Asynchronous Tests With Pytest

After finishing the dashboard, I hoped to quickly switch my datastore implementation to use the asynchronous methods for SQLAlchemy. Unfortunately, that was not the case. Everything needs to change, and that change must be done all at once. That includes pytest, that we need to run our tests.

To make the change more understandable, we start in this post with pytest and do the necessary work to test asynchronous methods. With that out of our way, we can next week focus on SQLAlchemy.

This post is part of my journey to learn Python. You find the code for this post in my PythonFriday repository on GitHub.

 

No native support for async methods

For our first steps with asynchronous tests in pytest, we can write a minimalistic test function that looks like this:

It may come as a surprise, but pytest does not support asynchronous functions directly. As soon as we add the async keyword in front of our test methods, we get this error:

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/how-to/capture-warnings.html

 

Install the support for asynchronous functions

We can solve this shortcoming with an additional package, like pytest-asyncio and install it with pip:

 

Add the async decorator

If we run our test again, we get the same error. We must add a decorator to tell pytest that it should run that test asynchronously:

With this additional line (and the import), pytest can finally run our test.

 

Asynchronous fixtures

There is one more problem waiting for us: fixtures. As long as we use a synchronous fixture with our asynchronous tests, everything works as expected. But as soon as we need to await something in the fixture, we get a new problem:

RuntimeWarning: coroutine ‘prepare’ was never awaited
item.funcargs = None # type: ignore[attr-defined]
Enable tracemalloc to get traceback where the object was allocated.
See https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings for more info.

— Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

We have an await in the fixture, but pytest does not know that this should be asynchronous. We can fix that problem with a different decorator:

Now our test with the fixture can run without any errors or warnings.

 

Next

With an additional package and two different decorators we can get pytest to support asynchronous tests. All the errors have helpful messages that guide us to the solution. Nevertheless, it is a frustrating experience when you first run into this shortcoming. I hope this post helps you to shorten your learning curve.

With this infrastructure problem out of our way, we can next week switch to an asynchronous SQLAlchemy.

1 thought on “Python Friday #239: Asynchronous Tests With Pytest”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.