Внедрение зависимостей с проблемой FastAPI

#python #python-3.x #dependency-injection #fastapi #pydantic

#python #python-3.x #внедрение зависимостей #fastapi #pydantic

Вопрос:

Я получаю сообщение об ошибке при использовании dependency_overrides https://fastapi.tiangolo.com/advanced/testing-dependencies /

У меня есть пример проекта (во вложении) со следующей структурой:

service.py

 from pydantic import BaseModel
class Service(BaseModel):
    key: int
    name: str
  

handler.py

 from service import Service

class ServiceHandler:
    async def get_all(self):
        return [Service(**x) for x in
                [{'key': 1, 'name': 'One'},
                 {'key': 2, 'name': 'Two'}]]
  

factory.py

 from fastapi import Depends
from handler import ServiceHandler

async def get_service_handler(handler=Depends(ServiceHandler)):
    return handler
  

main.py

 from fastapi import FastAPI, APIRouter, Depends
import factory

router = APIRouter()
@router.get("/services/", tags=["services"])
async def get_services(handler=Depends(factory.get_service_handler)):
    return await handler.get_all()

app = FastAPI()
app.include_router(router)

and pytest pytest-mock unit test for route:
  

unit_tests/test.py

 import...
@pytest.fixture(scope="session", autouse=True)
def client():
    return TestClient(app)

def test_get_services(client, mocker):
    handler = ServiceHandler()
    mocker.patch.object(handler, 'get_all')
    handler.get_all.return_value = [Service(_id=None, key=1, name='Test')]
    app.dependency_overrides[factory.get_service_handler] = handler

    response = client.get("/services")

    assert response.status_code == 200
    # expected = [{'_id': None, 'key': 1, 'name': 'Test'}]
    expected = [{'_id': None, 'key': 1, 'name': 'One'}, {'_id': None, 'key': 2, 'name': 'Two'}]
    assert response.json() == expected

    app.dependency_overrides = {}
  

Когда я запустил его с:
pytest unit_tests/test.py
Я получил исключение:
FAILED unit_tests/test.py::test_get_services - TypeError: <handler.ServiceHandler object at 0x7fd03d813b50> is not a callable object

Я попытался добавить вызов в handler.py следующим образом

 ...
class ServiceHandler:
    def __call__(self):
        pass
...
  

и произошло еще одно исключение:
FAILED unit_tests/test.py::test_get_services - AttributeError: 'NoneType' object has no attribute 'get_all'

В конце концов, если я запускаю сервер: hypercorn main:app --reload
http://127.0.0.1:8000/services/ все работает хорошо в любом случае.

Если я закомментирую строку в test.py:

 ...
app.dependency_overrides[factory.get_service_handler] = handler
...
  

тогда тест будет работать, поэтому я предполагаю, что причина проблемы заключается в dependency_overrides .

depends_demo.zip

Ответ №1:

Пожалуйста, замените содержимое вашего unit_tests/test.py :

 from fastapi.testclient import TestClient
from handler import ServiceHandler
import factory
from service import Service
import pytest
from main import app
import asyncio
from unittest.mock import AsyncMock
    


@pytest.fixture(scope="session", autouse=True)
def client():
    return TestClient(app)

def test_get_services(client, mocker):
    handler = ServiceHandler()

    async_mock = AsyncMock(return_value=[Service(_id=None, key=1, name='Test')])
    mocker.patch('handler.ServiceHandler.get_all', side_effect=async_mock)
    app.dependency_overrides[factory.get_service_handler()] = handler

    response = client.get("/services")

    assert response.status_code == 200
    # expected = [{'_id': None, 'key': 1, 'name': 'Test'}]
    expected = [{'_id': None, 'key': 1, 'name': 'One'}, {'_id': None, 'key': 2, 'name': 'Two'}]
    assert response.json() == expected

    app.dependency_overrides = {}

  

Две вещи, которые решили проблему:

  • Вам нужно вызвать get_service_handler, чтобы получить обработчик:

app.dependency_overrides[factory.get_service_handler()] = handler

  • Возможно, вам нужны асинхронные макеты для асинхронных функций, поэтому я прочитал об этом здесь: https://dino.codes/posts/mocking-asynchronous-functions-python /. Там говорится, что если вы выше python3.8, следует использовать AsyncMock:
 async_mock = AsyncMock(return_value=[Service(_id=None, key=1, name='Test')])
mocker.patch('handler.ServiceHandler.get_all', side_effect=async_mock)
  

Теперь я запускаю тесты с pytest unit_tests/test.py -v этим, что я получаю:

 E       AssertionError: assert [{'key': 1, 'name': 'Test'}] == [{'_id': None...name': 'Two'}]
E         At index 0 diff: {'key': 1, 'name': 'Test'} != {'_id': None, 'key': 1, 'name': 'One'}
E         Right contains one more item: {'_id': None, 'key': 2, 'name': 'Two'}
E         Full diff:
E         - [{'_id': None, 'key': 1, 'name': 'One'}, {'_id': None, 'key': 2, 'name': 'Two'}]
E           [{'key': 1, 'name': 'Test'}]