跳转至

Async Tests

Warning

The current page still doesn't have a translation for this language.

But you can help translating it: Contributing.

You have already seen how to test your FastAPI applications using the provided TestClient, but with it, you can't test or run any other async function in your (synchronous) pytest functions.

Being able to use asynchronous functions in your tests could be useful, for example, when you're querying your database asynchronously. Imagine you want to test sending requests to your FastAPI application and then verify that your backend successfully wrote the correct data in the database, while using an async database library.

Let's look at how we can make that work.

pytest-asyncio

If we want to call asynchronous functions in our tests, our test functions have to be asynchronous. Pytest provides a neat library for this, called pytest-asyncio, that allows us to specify that some test functions are to be called asynchronously.

You can install it via:

$ pip install pytest-asyncio

---> 100%

HTTPX

Even if your FastAPI application uses normal def functions instead of async def, it is still an async application underneath.

The TestClient does some magic inside to call the asynchronous FastAPI application in your normal def test functions, using standard pytest. But that magic doesn't work anymore when we're using it inside asynchronous functions. By running our tests asynchronously, we can no longer use the TestClient inside our test functions.

Luckily there's a nice alternative, called HTTPX.

HTTPX is an HTTP client for Python 3 that allows us to query our FastAPI application similarly to how we did it with the TestClient.

If you're familiar with the Requests library, you'll find that the API of HTTPX is almost identical.

The important difference for us is that with HTTPX we are not limited to synchronous, but can also make asynchronous requests.

Example

For a simple example, let's consider the following main.py module:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Tomato"}

The test_main.py module that contains the tests for main.py could look like this now:

import pytest
from httpx import AsyncClient

from .main import app


@pytest.mark.asyncio
async def test_root():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Tomato"}

Run it

You can run your tests as usual via:

$ pytest

---> 100%

In Detail

The marker @pytest.mark.asyncio tells pytest that this test function should be called asynchronously:

import pytest
from httpx import AsyncClient

from .main import app


@pytest.mark.asyncio
async def test_root():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Tomato"}

Tip

Note that the test function is now async def instead of just def as before when using the TestClient.

Then we can create an AsyncClient with the app, and send async requests to it, using await.

import pytest
from httpx import AsyncClient

from .main import app


@pytest.mark.asyncio
async def test_root():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        response = await ac.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Tomato"}

This is the equivalent to:

response = client.get('/')

that we used to make our requests with the TestClient.

Tip

Note that we're using async/await with the new AsyncClient - the request is asynchronous.

Other Asynchronous Function Calls

As the testing function is now asynchronous, you can now also call (and await) other async functions apart from sending requests to your FastAPI application in your tests, exactly as you would call them anywhere else in your code.

Tip

If you encounter a RuntimeError: Task attached to a different loop when integrating asynchronous function calls in your tests (e.g. when using MongoDB's MotorClient) check out this issue in the pytest-asyncio repository.