Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write pytest tests for a FastAPI route involving dependency injection with Pydantic models using Annotated and Depends?

I am trying to use pytest in order to test a simple API created in python with FastApi. I am running into two issues:

  • Main issue: if the function declares the input parameter with Annotated and Depends, I am unable to run the tests with pytest. The route responds with a 422 Unprocessable Entity status code.
  • Side issue: I keep getting a deprecation warning when I create the test client:
> DeprecationWarning: The 'app' shortcut is now deprecated. Use the explicit style 'transport=ASGITransport(app=...)' instead.

Please let me know how to solve these two issues. I read about dependency override but I am not sure if that is mandatory or how I could create multiple test cases for my app by varying the input in that approach (e.g.: test for different people in case my API should return different outputs for each).

In order to reproduce this error, I created a simple api in Python 3.10.13. This API has two routes with POST methods, one of which declares the input argument as a Pydantic Model of class Person directly (I can test this route, with a Deprecation warning), whileas the other one is using Annotated[Person, Depends()] (I do not know how to test this route, the test fails):

Here is the main.py file for my simple api:

# simple_api/main.py
from fastapi import FastAPI, Depends
from pydantic import BaseModel, Field
from typing import Annotated

# Define a simple Pydantic v2 model to validate the post request
class Person(BaseModel):
    name: str
    age: int = Field(..., ge=18, le = 130)

# Load app
app = FastAPI()

# I can test this route:
@app.post("/post_no_dependency")
def post_example_no_dependency(person: Person):
    return {
        "message": f"Hello {person.name}, your age is {person.age}",
        "method": "no dependency"
        }

# But I am not sure how to test this route:
@app.post("/post_with_dependency")
def post_example_with_dependency(
    person: Annotated[Person, Depends()]
    ):
    return {
        "message": f"Hello {person.name}, your age is {person.age}",
        "method":"with dependency"
        }

Here is the test file for my simple api:

# /simple_api/test_simple_api.py
import pytest
from httpx import AsyncClient

from main import app

@pytest.fixture
async def async_client():
  """Fixture to create a FastAPI test client."""
  async with AsyncClient(app=app, base_url="http://test") as async_test_client:
      yield async_test_client

# This test works
@pytest.mark.anyio
async def test_post_example_no_dependency(async_client: AsyncClient):
    data = {"name": "john doe",
            "age": 32}
    response = await async_client.post("/post_no_dependency",
                                       json = data)
    assert response.status_code == 200
    assert response.json()["method"] == "no dependency"

# This test fails because the status code is 422, not 200.
@pytest.mark.anyio
async def test_post_example_with_dependency(async_client: AsyncClient):
    data = {"name": "john doe",
            "age": 32}
    response = await async_client.post("/post_with_dependency",
                                       json = data)
    assert response.status_code == 200
    assert response.json()["method"] == "with dependency"
    

Here is what happens when I run pytest in a bash terminal (I am using a github codespace):

(simple_venv) @mikeliux ➜ /workspaces/simple_api $ pytest test_simple_api.py
==================================================================== test session starts ====================================================================
platform linux -- Python 3.10.13, pytest-8.1.1, pluggy-1.4.0
rootdir: /workspaces/simple_api
plugins: anyio-4.3.0
collected 4 items                                                                                                                                           

test_simple_api.py .F.F                                                                                                                               [100%]

========================================================================= FAILURES ==========================================================================
________________________________________________________ test_post_example_with_dependency[asyncio] _________________________________________________________

async_client = <httpx.AsyncClient object at 0x7f0e0cd893f0>

    @pytest.mark.anyio
    async def test_post_example_with_dependency(async_client: AsyncClient):
        data = {"name": "john doe",
                "age": 32}
        response = await async_client.post("/post_with_dependency",
                                           json = data)
>       assert response.status_code == 200
E       assert 422 == 200
E        +  where 422 = <Response [422 Unprocessable Entity]>.status_code

test_simple_api.py:30: AssertionError
__________________________________________________________ test_post_example_with_dependency[trio] __________________________________________________________

async_client = <httpx.AsyncClient object at 0x7f0e0c436770>

    @pytest.mark.anyio
    async def test_post_example_with_dependency(async_client: AsyncClient):
        data = {"name": "john doe",
                "age": 32}
        response = await async_client.post("/post_with_dependency",
                                           json = data)
>       assert response.status_code == 200
E       assert 422 == 200
E        +  where 422 = <Response [422 Unprocessable Entity]>.status_code

test_simple_api.py:30: AssertionError
===================================================================== warnings summary ======================================================================
test_simple_api.py::test_post_example_no_dependency[asyncio]
test_simple_api.py::test_post_example_with_dependency[asyncio]
test_simple_api.py::test_post_example_no_dependency[trio]
test_simple_api.py::test_post_example_with_dependency[trio]
  /workspaces/simple_api/simple_venv/lib/python3.10/site-packages/httpx/_client.py:1426: DeprecationWarning: The 'app' shortcut is now deprecated. Use the explicit style 'transport=ASGITransport(app=...)' instead.
    warnings.warn(message, DeprecationWarning)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================== short test summary info ==================================================================
FAILED test_simple_api.py::test_post_example_with_dependency[asyncio] - assert 422 == 200
FAILED test_simple_api.py::test_post_example_with_dependency[trio] - assert 422 == 200
========================================================== 2 failed, 2 passed, 4 warnings in 0.77s ==========================================================

Also, you can check the installed packages here:

(simple_venv) @mikeliux ➜ /workspaces/simple_api $ pip freeze
annotated-types==0.6.0
anyio==4.3.0
attrs==23.2.0
certifi==2024.2.2
click==8.1.7
dnspython==2.6.1
email_validator==2.1.1
exceptiongroup==1.2.0
fastapi==0.110.0
h11==0.14.0
httpcore==1.0.5
httptools==0.6.1
httpx==0.27.0
idna==3.6
iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.3
MarkupSafe==2.1.5
orjson==3.10.0
outcome==1.3.0.post0
packaging==24.0
pluggy==1.4.0
pydantic==2.6.4
pydantic-extra-types==2.6.0
pydantic-settings==2.2.1
pydantic_core==2.16.3
pytest==8.1.1
python-dotenv==1.0.1
python-multipart==0.0.9
PyYAML==6.0.1
sniffio==1.3.1
sortedcontainers==2.4.0
starlette==0.36.3
tomli==2.0.1
trio==0.25.0
typing_extensions==4.10.0
ujson==5.9.0
uvicorn==0.29.0
uvloop==0.19.0
watchfiles==0.21.0
websockets==12.0

Thank you in advance

like image 749
mikeliux Avatar asked Sep 18 '25 12:09

mikeliux


1 Answers

I managed to solve the Side issue (DeprecationWarning) by making these changes in the test file:

# /simple_api/test_simple_api.py
import pytest
from httpx import AsyncClient, ASGITransport # additional import

from main import app

@pytest.fixture
async def async_client():
  """Fixture to create a FastAPI test client."""
  # instead of app = app, use this to avoid the DeprecationWarning:
  async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as async_test_client:
      yield async_test_client

Still, the Main issue remains unsolved. Here is the updated result:

(simple_venv) @mikeliux ➜ /workspaces/simple_api $ pytest test_simple_api.py
==================================================================== test session starts ====================================================================
platform linux -- Python 3.10.13, pytest-8.1.1, pluggy-1.4.0
rootdir: /workspaces/simple_api
plugins: anyio-4.3.0
collected 4 items                                                                                                                                           

test_simple_api.py .F.F                                                                                                                               [100%]

========================================================================= FAILURES ==========================================================================
________________________________________________________ test_post_example_with_dependency[asyncio] _________________________________________________________

async_client = <httpx.AsyncClient object at 0x7f837f36d330>

    @pytest.mark.anyio
    async def test_post_example_with_dependency(async_client: AsyncClient):
        data = {"name": "john doe",
                "age": 32}
        response = await async_client.post("/post_with_dependency",
                                           json = data)
>       assert response.status_code == 200
E       assert 422 == 200
E        +  where 422 = <Response [422 Unprocessable Entity]>.status_code

test_simple_api.py:31: AssertionError
__________________________________________________________ test_post_example_with_dependency[trio] __________________________________________________________

async_client = <httpx.AsyncClient object at 0x7f837f3e6500>

    @pytest.mark.anyio
    async def test_post_example_with_dependency(async_client: AsyncClient):
        data = {"name": "john doe",
                "age": 32}
        response = await async_client.post("/post_with_dependency",
                                           json = data)
>       assert response.status_code == 200
E       assert 422 == 200
E        +  where 422 = <Response [422 Unprocessable Entity]>.status_code

test_simple_api.py:31: AssertionError
================================================================== short test summary info ==================================================================
FAILED test_simple_api.py::test_post_example_with_dependency[asyncio] - assert 422 == 200
FAILED test_simple_api.py::test_post_example_with_dependency[trio] - assert 422 == 200
================================================================ 2 failed, 2 passed in 0.78s ================================================================
like image 149
mikeliux Avatar answered Sep 21 '25 02:09

mikeliux