You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

FastAPI单元测试中正常Payload请求返回404状态码的问题排查求助

FastAPI单元测试中正常Payload请求返回404状态码的问题排查求助

我在用pytest给FastAPI的路由写单元测试,8个测试里7个都顺利通过了,但唯独正常Payload的场景一直返回404,导致status_code == 200的断言失败。特别奇怪的是,同一个端点的其他测试(比如带缺失字段的错误Payload)能正常返回预期的400状态码,这说明路由本身是能被命中的,实在搞不懂为什么合法请求会触发404。

项目结构

├── .gitignore
├── README.md
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── pdf.py
│   ├── templates.py
│   ├── test_main.py
├── endpoints
│   ├── statement.py
│   ├── template2.py
│   ├── template3.py
├── templates
│   ├── statement.html
│   ├── template2.html
│   ├── template3.html

关键代码片段

main.py

from fastapi import FastAPI
from contextlib import asynccontextmanager
from typing import AsyncIterator
import logging
from . import templates
from app.endpoints import statement

# 假设lifespan函数已定义,此处省略实现
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
    logging.info("Service starting")
    yield
    logging.info("Service stopping")

app = FastAPI(
    title="My App",
    description="Template to PDF",
    version="1.0.0",
    lifespan=lifespan,
)
app.include_router(statement.router)

@app.get("/", status_code=200)
async def root():
    return {
        "service": "HTML Template to PDF Converter",
        "status": "running",
        "loaded_templates": list(templates.get_all().keys()),
    }

@app.get("/healthcheck", status_code=200)
async def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=80, log_level="info")

endpoints/statement.py

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

router = APIRouter(prefix="/statement", tags=["statement"])

# 定义请求Payload的Pydantic模型
class StatementPayload(BaseModel):
    docId: str
    table1: list[dict]
    table2: list[dict]
    # 其他字段省略

@router.post("/")
async def generate_statement(payload: StatementPayload):
    # 简单的Payload校验
    if not bool(payload.table1):
        raise HTTPException(
            status_code=400,
            detail="Table 1 missing",
        )
    if not bool(payload.table2):
        raise HTTPException(status_code=400, detail="Table 2 missing")
    
    # 生成PDF的核心逻辑,此处省略
    return {"status": "success", "pdf_url": "/path/to/pdf"}

test_main.py

import pytest
import pytest_check as check
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

@pytest.fixture
def good_statement():
    return {
        "docId": "STATEMENT",
        "table1": [{"id": 1, "content": "test data 1"}],
        "table2": [{"id": 1, "content": "test data 2"}],
        # 其他必填字段均已正确填充
    }

@pytest.fixture
def statement_missing_table1():
    return {
        "docId": "STATEMENT",
        "table1": [],
        "table2": [{"id": 1, "content": "test data 2"}],
        # 其他字段省略
    }

def test_root():
    response = client.get("/")
    expected_subset = {"service": "HTML Template to PDF Converter", "status": "running"}
    check.equal(response.status_code, 200)
    check.greater_equal(response.json().items(), expected_subset.items())

def test_healthcheck():
    response = client.get("/healthcheck")
    assert response.status_code == 200
    assert response.json() == {"status": "healthy"}

def test_generate_statement_success(good_statement):
    # 👇 这个测试一直失败,返回404而非预期的200
    response = client.post("/statement", json=good_statement)
    check.equal(response.status_code, 200)
    check.equal(response.headers["Content-Type"], "application/json")

def test_generate_statement_missing_table1(statement_missing_table1):
    response = client.post("/statement", json=statement_missing_table1)
    assert response.status_code == 400
    assert response.json()["detail"] == "Table 1 missing"

# 其他5个测试用例均通过,此处省略

Pytest 输出

============================= test session starts ==============================
collected 8 items

test_main.py .......F                                                     [100%]

=================================== FAILURES ===================================
____________________ test_generate_statement_success _____________________

    def test_generate_statement_success(good_statement):
        # 👇 这个测试一直失败,返回404而非预期的200
        response = client.post("/statement", json=good_statement)
>       check.equal(response.status_code, 200)
E       AssertionError: 404 != 200

test_main.py:32: AssertionError
=========================== short test summary info ============================
FAILED test_main.py::test_generate_statement_success - AssertionError: 404 != 200
========================= 1 failed, 7 passed in 0.12s ==========================

我的疑问

  • 同一个/statement POST路由,错误Payload的测试能正确返回400,说明路由注册是有效的
  • 正常Payload的请求却返回404,排除了路由不存在的可能
  • 已经确认good_statement fixture返回的Payload包含所有必填字段,格式完全符合Pydantic模型要求

有没有大佬能帮我分析下可能的原因?实在卡在这里了😭

火山引擎 最新活动