Тестирование FastAPI
FastAPI основан на StarletteTestClientПредоставляет удобную поддержку тестирования, позволяя тестировать запросы и ответы API без запуска сервера.
Установите зависимости
TestClientна основеhttpx, необходимо установить:
pip install httpx
Базовый тест
ИспользованиеTestClientСоздайте тестовый клиент, затем используйте его следующим образомhttpxотправьте запрос таким же образом:
Пример
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
# Создайте тестовый клиент
client = TestClient(app)
# Тестовая функция
def test_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello World"}
TestClientНапрямую вызывает приложение FastAPI, минуя сетевой уровень, поэтому тесты выполняются очень быстро. Вам не нужно запускать сервер Uvicorn.
Тестирование различных типов запросов
Пример
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.post("/items/")
async def create_item(item: Item):
return {"name": item.name, "price": item.price}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, "name": item.name}
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"deleted": item_id}
client = TestClient(app)
# Тест POST-запроса
def test_create_item():
response = client.post(
"/items/",
json={"name": "Foo", "price": 42.0}, # Отправить тело JSON-запроса
)
assert response.status_code == 200
assert response.json() == {"name": "Foo", "price": 42.0}
# Тест GET-запроса
def test_read_item():
response = client.get("/items/1")
assert response.status_code == 200
assert response.json() == {"item_id": 1}
# Тест PUT-запроса
def test_update_item():
response = client.put(
"/items/1",
json={"name": "Bar", "price": 50.0},
)
assert response.status_code == 200
# Тестирование DELETE-запроса
def test_delete_item():
response = client.delete("/items/1")
assert response.status_code == 200
assert response.json() == {"deleted": 1}
Тестирование параметров запроса и заголовков запроса
Пример
from fastapi import FastAPI, Header
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/items/")
async def read_items(
skip: int = 0,
limit: int = 10,
x_token: Annotated[str | None, Header()] = None,
):
return {"skip": skip, "limit": limit, "x_token": x_token}
client = TestClient(app)
# Проверка параметров запроса
def test_read_items_with_params():
response = client.get("/items/?skip=5&limit=20")
assert response.status_code == 200
assert response.json() == {"skip": 5, "limit": 20, "x_token": None}
# Проверка заголовков запроса
def test_read_items_with_header():
response = client.get(
"/items/",
headers={"X-Token": "fake-token"}, # Отправка пользовательских заголовков запроса
)
assert response.status_code == 200
assert response.json()["x_token"] == "fake-token"
Проверка ответов с исключениями
Пример
from fastapi.testclient import TestClient
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id == 0:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id}
client = TestClient(app)
# Проверка нормального ответа
def test_read_item():
response = client.get("/items/1")
assert response.status_code == 200
# Тест ответа при ошибке
def test_read_item_not_found():
response = client.get("/items/0")
assert response.status_code == 404
assert response.json() == {"detail": "Элемент не найден"}
# Тест ошибки валидации
def test_read_item_invalid_id():
response = client.get("/items/foo")
assert response.status_code == 422 # Ошибка валидации
Запустите тесты с помощью pytest
Сохраните тестовый код какtest_main.py, затем запустите:
$ pytest test_main.py # 显示详细输出 $ pytest test_main.py -v # 显示 print 输出 $ pytest test_main.py -s
pytest автоматически обнаружит файлы и функцииtest_имена которых начинаются с префикса "test_".
Переопределение зависимостей в тестах
В тестах может потребоваться переопределить некоторые зависимости (например, подключения к базе данных, логику аутентификации):
Пример
from fastapi.testclient import TestClient
app = FastAPI()
# Фактическая функция зависимости
def get_query_param():
return "real_value"
@app.get("/items/")
async def read_items(query: str = Depends(get_query_param)):
return {"query": query}
client = TestClient(app)
def test_with_override():
# Переопределение зависимостей
app.dependency_overrides[get_query_param] = lambda: "test_value"
response = client.get("/items/")
assert response.json() == {"query": "test_value"}
# Очистить переопределения
app.dependency_overrides.clear()
app.dependency_overridesПозволяет заменять любую функцию зависимости во время тестирования, что идеально подходит для имитации таких сценариев, как подключения к базе данных и вызовы внешних API. Не забудьте очистить переопределения после тестирования.
Итог
- Использование
TestClientТестирование приложения FastAPI напрямую без запуска сервера. - Поддерживает тестирование всех методов HTTP, включая GET, POST, PUT, DELETE и т. д.
- Позволяет тестировать параметры запроса, заголовки запроса, тела запросов, ответы с исключениями и т. д.
- Использование
app.dependency_overridesПереопределение зависимостей в тестах - С pytest можно легко организовывать и запускать тесты.