FastAPI 系列

  1. FastAPI 系列:从第一个接口到生产部署

  2. FastAPI 为什么适合做 JSON API

  3. 第一个接口怎么跑起来

  4. 路径、查询和请求体为什么要分开声明

  5. Pydantic 模型怎么当契约

  6. 路由怎么拆

  7. 依赖注入到底省了什么

  8. 数据库会话怎么进接口

  9. 状态码和异常为什么不要裸 raise

  10. 鉴权怎么接到 Depends 上

  11. 中间件、CORS 和后台任务分别解决什么

  12. 测试怎么写才不连真实库(当前篇)

  13. 生产怎么部署

接口测试应验证状态码、JSON 形状和鉴权分支,而不是验证 Postgres 是否在线。TestClient 在进程内打 ASGI,dependency_overridesget_db / get_current_user 换成假对象。真实数据库留给少量集成测试。

pytest 通过 TestClient 调用应用,并用 dependency_overrides 替换 get_db

最小测试

from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


def test_health():
    response = client.get("/health")
    assert response.status_code == 200
    assert response.json() == {"status": "ok"}

相对导入很重要:测试文件应能 from app.main import app,而不是把项目根随便塞进 sys.path

覆盖数据库依赖

from app.db import get_db


class FakeSession:
    def get(self, model, item_id):
        if item_id == 1:
            return {"id": 1, "name": "demo"}
        return None


def override_get_db():
    yield FakeSession()


def test_read_item(client):
    app.dependency_overrides[get_db] = override_get_db
    response = client.get("/items/1")
    assert response.status_code == 200
    app.dependency_overrides.clear()

用 pytest fixture 做 setup/teardown,避免测试互相污染 overrides。

覆盖当前用户

from app.auth import get_current_user


def test_me_ok():
    app.dependency_overrides[get_current_user] = lambda: User(username="alice")
    response = client.get("/users/me")
    assert response.status_code == 200
    assert response.json()["username"] == "alice"
    app.dependency_overrides.clear()


def test_me_unauthorized():
    response = client.get("/users/me")
    assert response.status_code == 401

未覆盖时,OAuth2PasswordBearer 会因缺少 Authorization 返回 401,这正好覆盖「没带 Token」分支。不要为了省事在测试里把鉴权中间件拆掉。

校验错误也要测

def test_create_item_invalid():
    response = client.post("/items/", json={"name": ""})
    assert response.status_code == 422
    assert response.json()["detail"]

422 的结构是契约的一部分。改了模型约束,测试应一起红。

常见误区

每个测试都启动 uvicorn 再 curl

慢、端口冲突、难并行。TestClient 就是为这个存在的。

覆盖错对象

必须覆盖定义 Depends(get_db) 时用的那个函数对象。复制粘贴了一个同名函数不会生效。

测试依赖真实 JWT 密钥

单测覆盖 get_current_user;另写一两个测试专门喂过期 Token,不必让所有用例都走加密。

小结

单测替换依赖,集成测试才碰真实库。下一篇把 fastapi dev 换成可水平扩展的生产启动方式。

参考资料