FastAPI Testing

FastAPI based on StarletteTestClientProvides convenient testing support, allowing you to test API requests and responses without starting a server.


Install dependencies

TestClientbased onhttpx, you need to install:

pip install httpx

Basic test

UsageTestClientCreate a test client, then use it likehttpxsend the request in the same way:

Example

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}


# Create a test client
client = TestClient(app)


# Test Function
def test_root():
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

TestClientDirectly invokes the FastAPI application, bypassing the network layer, so tests run very fast. You don't need to start the Uvicorn server.


Test various request types

Example

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)


# Test POST request
def test_create_item():
    response = client.post(
        "/items/",
        json={"name": "Foo", "price": 42.0},  # Send JSON request body
    )
    assert response.status_code == 200
    assert response.json() == {"name": "Foo", "price": 42.0}


# Test GET request
def test_read_item():
    response = client.get("/items/1")
    assert response.status_code == 200
    assert response.json() == {"item_id": 1}


# Test PUT request
def test_update_item():
    response = client.put(
        "/items/1",
        json={"name": "Bar", "price": 50.0},
    )
    assert response.status_code == 200


# Test DELETE request
def test_delete_item():
    response = client.delete("/items/1")
    assert response.status_code == 200
    assert response.json() == {"deleted": 1}

Test query parameters and request headers

Example

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)


# Test query parameters
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}


# Test request headers
def test_read_items_with_header():
    response = client.get(
        "/items/",
        headers={"X-Token": "fake-token"},  # Send custom request headers
    )
    assert response.status_code == 200
    assert response.json()["x_token"] == "fake-token"

Test exception responses

Example

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)


# Test normal response
def test_read_item():
    response = client.get("/items/1")
    assert response.status_code == 200


# Test error response
def test_read_item_not_found():
    response = client.get("/items/0")
    assert response.status_code == 404
    assert response.json() == {"detail": "Item not found"}


# Test validation error
def test_read_item_invalid_id():
    response = client.get("/items/foo")
    assert response.status_code == 422  # Validation Failed

Run tests using pytest

Save the test code astest_main.py, then run:

$ pytest test_main.py

# 显示详细输出
$ pytest test_main.py -v

# 显示 print 输出
$ pytest test_main.py -s

pytest will automatically discover files and functionstest_whose names start with the "test_" prefix.


Override dependencies in tests

In tests, you may need to override certain dependencies (such as database connections, authentication logic):

Example

from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient

app = FastAPI()


# Actual dependency function
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():
    # Override Dependencies
    app.dependency_overrides[get_query_param] = lambda: "test_value"

    response = client.get("/items/")
    assert response.json() == {"query": "test_value"}

    # Clear Overrides
    app.dependency_overrides.clear()

app.dependency_overridesAllows you to replace any dependency function during testing, making it ideal for simulating scenarios like database connections and external API calls. Remember to clear the overrides after testing.


Summary

  • UsageTestClientDirectly test the FastAPI application without starting a server.
  • Supports testing of all HTTP methods, including GET, POST, PUT, DELETE, etc.
  • Can test query parameters, request headers, request bodies, exception responses, etc.
  • Usageapp.dependency_overridesOverride dependencies in tests
  • With pytest, you can easily organize and run tests.
other extensions