Асинхронный тестовый клиент для FastApi
Получаю ошибку в тесте: AttributeError: 'async_generator' object has no attribute 'get'
Вот тест:
@pytest.mark.asyncio
async def test_get_profile_route(self, client: AsyncClient):
response = await client.get("/api/v1/profile/2")
assert response.status_code == 200
conftest.py:
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from alembic import command
from alembic.config import Config
from config.database.db_helper import db_helper
from main import get_application
from src.models import Base
DATABASE_URL = "postgresql+asyncpg://test_user:password@localhost/test_db"
engine = create_async_engine(DATABASE_URL, echo=True)
AsyncTestingSessionLocal = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
@pytest.fixture(scope="session")
def app():
_app = get_application()
return _app
@pytest.fixture(scope="session")
def alembic_config():
config = Config("alembic.ini")
config.set_main_option("sqlalchemy.url", DATABASE_URL)
return config
@pytest.fixture(scope="session")
async def prepare_database(alembic_config):
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
command.upgrade(alembic_config, "head")
@pytest.fixture(scope="session")
async def db_session(prepare_database):
await prepare_database
async with AsyncTestingSessionLocal() as session:
async with session.begin():
yield session
@pytest.fixture(scope='session')
async def client(app, db_session) -> AsyncClient:
async def override_get_db():
yield db_session
app.dependency_overrides[db_helper.get_async_session] = override_get_db
async with AsyncClient(app=app, base_url="http://test", transport=ASGITransport(app)) as c:
yield c
app.dependency_overrides.clear()
Ответы (1 шт):
Проблема в том, что фикстура у вас асинхронная, но pytest сам по себе про асинхронные фикстуры ничего не знает.
Вот минимальный пример с такой проблемой:
import pytest
@pytest.fixture
async def some_async_fixture():
yield {"some": "data"}
@pytest.mark.asyncio
async def test_something(some_async_fixture):
assert some_async_fixture.get("some") == "data"
При запуске теста падает с ошибкой (не обязательно всегда будет писать про get
, просто подобрал пример, чтобы имя метода совпадало):
❯ pytest test.py
================================== test session starts ===================================
...
collected 1 item
test.py F [100%]
======================================== FAILURES ========================================
_____________________________________ test_something _____________________________________
some_async_fixture = <async_generator object some_async_fixture at 0x7f291e34a040>
@pytest.mark.asyncio
async def test_something(some_async_fixture):
> assert some_async_fixture.get("some") == "data"
E AttributeError: 'async_generator' object has no attribute 'get'
test.py:11: AttributeError
================================ short test summary info =================================
FAILED test.py::test_something - AttributeError: 'async_generator' object has no attribute 'get'
=================================== 1 failed in 0.06s ====================================
Вариант решения через замену декоратора фикстуры
Решается либо заменой декоратора у фикстуры на @pytest_asyncio.fixture
:
import pytest
import pytest_asyncio
@pytest_asyncio.fixture
async def some_async_fixture():
yield {"some": "data"}
@pytest.mark.asyncio
async def test_something(some_async_fixture):
assert some_async_fixture.get("some") == "data"
Вывод:
❯ pytest test.py
================================== test session starts ===================================
...
collected 1 item
test.py . [100%]
=================================== 1 passed in 0.01s ====================================
Решение через конфигурацию
Либо без изменения кода проблему можно решить добавлением asyncio_mode = auto
в конфиг pytest, либо в файл pytest.ini
:
[pytest]
asyncio_mode = auto
либо в pyproject.toml
:
[tool.pytest.ini_options]
asyncio_mode = "auto"
Кавычки вокруг auto
имеют значение: в ini
их не должно быть, в toml
- должны быть.
После добавления этой опции добавление декоратора @pytest.mark.asyncio
к асинхронным тестам становится необязательным.