fastapi:测试:一个完整的测试异步接口的例子

一,安装用到的库

$ pip install pytest pytest-asyncio httpx sqlalchemy aiomysql

二,代码

结构:

image

conftest.py

# tests/conftest.py (API 专有)
import asyncio

import pytest
import pytest_asyncio
from httpx import AsyncClient, ASGITransport
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession

from app.api.main import api_app
from app.core.database import Base, get_db
from main import app

# 使用独立的测试数据库
# TEST_DATABASE_URL = "mysql+aiomysql://root:rootpassword@127.0.0.1:3306/test_db"
TEST_DATABASE_URL = "mysql+aiomysql://root:rootpassword@localhost:3306/media_bank"
test_engine = create_async_engine(TEST_DATABASE_URL, echo=False)
TestingSessionLocal = async_sessionmaker(bind=test_engine, expire_on_commit=False)

# 初始化表结构(通常在测试套件开始时运行一次)
@pytest_asyncio.fixture(scope="session", autouse=True)
async def init_test_database():
    async with test_engine.begin() as conn:
        # 测试前:清空并重新创建所有表
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
    yield
    async with test_engine.begin() as conn:
        # 测试结束后:可选清理
        await conn.run_sync(Base.metadata.drop_all)


# 核心:每次测试独立的 AsyncSession,并利用外部事务自动回滚
@pytest_asyncio.fixture(scope="function")
async def db_session() -> AsyncSession:
    async with test_engine.connect() as connection:
        # 开启一个根事务
        transaction = await connection.begin()
        # 将 session 绑定到这个连接上
        async with TestingSessionLocal(bind=connection) as session:
            yield session
            # 测试完成后,无条件回滚!数据库不会留下任何痕迹
            await transaction.rollback()


# 核心:异步 HTTP 客户端
@pytest_asyncio.fixture(scope="function")
async def async_client(db_session: AsyncSession):
    # 重写 FastAPI 的依赖项,注入带有自动回滚功能的 db_session
    async def _get_test_db():
        yield db_session

    api_app.dependency_overrides[get_db] = _get_test_db

    # 使用 httpx.AsyncClient 替代 TestClient
    async with AsyncClient(transport=ASGITransport(app=api_app), base_url="http://127.0.0.1:8000") as client:
        yield client

    api_app.dependency_overrides.clear()

test_users.py


import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

from app.models import User

# 必须为异步测试指定 pytest 标记
pytestmark = pytest.mark.asyncio

async def test_login_user_success(async_client: AsyncClient, db_session: AsyncSession):

    # 2. 调用异步接口
    payload = {"username": "admin", "password": "123456"}
    response = await async_client.post("/api/account/login", data=payload)

    # 3. 断言
    print("response:", response)
    print("status_code:", response.status_code)
    assert response.status_code == 200

三,测试效果:

$ pytest
=========================================================================== test session starts ===========================================================================
platform linux -- Python 3.10.19, pytest-9.1.1, pluggy-1.6.0
rootdir: /data/python/fastapi/demo1
configfile: pytest.ini (WARNING: ignoring pytest config in pyproject.toml!)
plugins: anyio-4.13.0, asyncio-1.4.0
asyncio: mode=auto, debug=False, asyncio_default_fixture_loop_scope=session, asyncio_default_test_loop_scope=function
collected 1 item                                                                                                                                                          

tests/api/test_users.py .                                                                                                                                           [100%]

============================================================================ warnings summary =============================================================================
app/api/v1/users.py:68
  /data/python/fastapi/demo1/app/api/v1/users.py:68: PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.13/migration/
    class UserResponse(BaseModel):

app/api/v1/users.py:93
  /data/python/fastapi/demo1/app/api/v1/users.py:93: PydanticDeprecatedSince20: Support for class-based `config` is deprecated, use ConfigDict instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.13/migration/
    class AssetResponse(BaseModel):

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
====================================================================== 1 passed, 2 warnings in 0.42s ======================================================================

 

posted @ 2026-07-28 12:53  刘宏缔的架构森林  阅读(5)  评论(0)    收藏  举报