Тестирование FastAPI

FastAPI основан на StarletteTestClientПредоставляет удобную поддержку тестирования, позволяя тестировать запросы и ответы API без запуска сервера.


Установите зависимости

TestClientна основеhttpx, необходимо установить:

pip install httpx

Базовый тест

ИспользованиеTestClientСоздайте тестовый клиент, затем используйте его следующим образомhttpxотправьте запрос таким же образом:

Пример

from fastapi import FastAPI
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 import FastAPI
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 typing import Annotated
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 import FastAPI, HTTPException
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 import Depends, FastAPI
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 можно легко организовывать и запускать тесты.
Другие расширения