fastapi/tests/test_dependency_service_ove...

224 lines
6.5 KiB
Python

from abc import ABC, abstractmethod
from typing import List, runtime_checkable
import pytest
from fastapi import APIRouter, FastAPI, Service
from fastapi.testclient import TestClient
from pydantic import BaseModel
from typing_extensions import Protocol
app = FastAPI()
router = APIRouter()
class Item(BaseModel):
name: str
value: int
@runtime_checkable
class ItemsProtocol(Protocol):
def get_items(self) -> List[Item]:
... # pragma: nocover
class ItemsInterface(ABC):
@abstractmethod
def get_items(self) -> List[Item]:
... # pragma: nocover
class ItemsService(ItemsInterface):
def __init__(self, name: str, value: int):
self.name = name
self.value = value
def get_items(self) -> List[Item]:
return [Item(name=self.name, value=self.value)]
class ClassNestedProtocol:
def __init__(self, impl: ItemsProtocol = Service()):
self.impl = impl
class ClassNestedInterface:
def __init__(self, impl: ItemsInterface = Service()):
self.impl = impl
class ClassNested:
def __init__(self, impl: ItemsService = Service()):
self.impl = impl
@app.get("/depends-on-protocol/")
async def depends_on_protocol(service: ItemsProtocol = Service()):
return {"in": "depends-on-protocol", "items": service.get_items()}
@app.get("/depends-on-interface/")
async def depends_on_interface(service: ItemsInterface = Service()):
return {"in": "depends-on-interface", "items": service.get_items()}
@app.get("/depends-on-class/")
async def depends_on_class(service: ItemsService = Service()):
return {"in": "depends-on-class", "items": service.get_items()}
@app.get("/depends-on-nested-protocol/")
async def depends_on_nested_protocol(service: ClassNestedProtocol = Service()):
return {"in": "depends-on-nested-protocol", "items": service.impl.get_items()}
@app.get("/depends-on-nested-interface/")
async def depends_on_nested_interface(service: ClassNestedInterface = Service()):
return {"in": "depends-on-nested-interface", "items": service.impl.get_items()}
@app.get("/depends-on-nested-class/")
async def depends_on_nested_class(service: ClassNested = Service()):
return {"in": "depends-on-nested-class", "items": service.impl.get_items()}
app.include_router(router)
client = TestClient(app)
def test_depends_on_protocol_no_override():
with pytest.raises(TypeError, match="Protocols cannot be instantiated"):
# not 422 error about missing args and kwargs inputs
client.get("/depends-on-protocol/")
def test_depends_on_protocol_override():
app.dependency_overrides[ItemsProtocol] = lambda: ItemsService(name="abc", value=1)
response = client.get("/depends-on-protocol/")
assert response.status_code == 200
assert response.json() == {
"in": "depends-on-protocol",
"items": [
{
"name": "abc",
"value": 1,
},
],
}
app.dependency_overrides = {}
def test_depends_on_interface_no_override():
error_msg = "Can't instantiate abstract class ItemsInterface with.*abstract method"
with pytest.raises(TypeError, match=error_msg):
client.get("/depends-on-interface/")
def test_depends_on_interface_override():
app.dependency_overrides[ItemsInterface] = lambda: ItemsService(name="abc", value=1)
response = client.get("/depends-on-interface/")
assert response.status_code == 200
assert response.json() == {
"in": "depends-on-interface",
"items": [
{
"name": "abc",
"value": 1,
},
],
}
app.dependency_overrides = {}
def test_depends_on_class_no_override():
error_msg = "missing 2 required positional arguments: 'name' and 'value'"
with pytest.raises(TypeError, match=error_msg):
# not 422 error about missing body fields
client.get("/depends-on-class/")
def test_depends_on_class_override():
app.dependency_overrides[ItemsService] = lambda: ItemsService(name="abc", value=1)
response = client.get("/depends-on-class/")
assert response.status_code == 200
assert response.json() == {
"in": "depends-on-class",
"items": [
{
"name": "abc",
"value": 1,
},
],
}
app.dependency_overrides = {}
def test_depends_on_nested_protocol_no_override():
with pytest.raises(TypeError, match="Protocols cannot be instantiated"):
client.get("/depends-on-nested-protocol/")
def test_depends_on_nested_protocol_override_top_level():
service = ClassNestedProtocol(impl=ItemsService(name="abc", value=1))
app.dependency_overrides[ClassNestedProtocol] = lambda: service
response = client.get("/depends-on-nested-protocol/")
assert response.status_code == 200
assert response.json() == {
"in": "depends-on-nested-protocol",
"items": [
{
"name": "abc",
"value": 1,
},
],
}
app.dependency_overrides = {}
def test_depends_on_nested_interface_no_override():
error_msg = "Can't instantiate abstract class ItemsInterface with.*abstract method"
with pytest.raises(TypeError, match=error_msg):
client.get("/depends-on-nested-interface/")
def test_depends_on_nested_interface_override_top_level():
service = ClassNestedInterface(impl=ItemsService(name="abc", value=1))
app.dependency_overrides[ClassNestedInterface] = lambda: service
response = client.get("/depends-on-nested-interface/")
assert response.status_code == 200
assert response.json() == {
"in": "depends-on-nested-interface",
"items": [
{
"name": "abc",
"value": 1,
},
],
}
app.dependency_overrides = {}
def test_depends_on_nested_class_no_override():
error_msg = "missing 2 required positional arguments: 'name' and 'value'"
with pytest.raises(TypeError, match=error_msg):
client.get("/depends-on-nested-class/")
def test_depends_on_nested_class_override_top_level():
service = ClassNested(impl=ItemsService(name="abc", value=1))
app.dependency_overrides[ClassNested] = lambda: service
response = client.get("/depends-on-nested-class/")
assert response.status_code == 200
assert response.json() == {
"in": "depends-on-nested-class",
"items": [
{
"name": "abc",
"value": 1,
},
],
}
app.dependency_overrides = {}