FastAPI测试策略:参数解析单元测试

# 第一层:模型级测试
def test_user_model_validation():
with pytest.raises(ValidationError):
User(age=-5)
# 第二层:依赖项测试
def test_auth_dependency():
assert auth_dependency(valid_token).status == "active"
# 第三层:端点集成测试
def test_user_endpoint():
response = client.get("/users/1")
assert response.json()["id"] == 1
import pytest
@pytest.mark.parametrize("input,expected", [
("admin", 200),
("guest", 403),
("invalid", 401)
])
def test_role_based_access(input, expected):
response = client.get(
"/admin",
headers={"X-Role": input}
)
assert response.status_code == expected
from fastapi.testclient import TestClient
def test_multi_part_form():
response = TestClient(app).post(
"/upload",
files={"file": ("test.txt", b"content")},
data={"name": "test"}
)
assert response.status_code == 201
def test_graphql_query():
response = client.post(
"/graphql",
json={"query": "query { user(id:1) { name } }"}
)
assert "errors" not in response.json()
class AuthTestClient(TestClient):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.token = generate_test_token()
def get(self, url, **kwargs):
headers = kwargs.setdefault("headers", {})
headers.setdefault("Authorization", f"Bearer {self.token}")
return super().get(url, **kwargs)
test_client = AuthTestClient(app)
def test_custom_validator():
with pytest.raises(ValidationError) as excinfo:
Product(stock=-10)
assert "库存不能为负" in str(excinfo.value)
def test_regex_validation():
valid = {"email": "test@example.com"}
invalid = {"email": "invalid-email"}
assert EmailRequest(**valid)
with pytest.raises(ValidationError):
EmailRequest(**invalid)
class BaseUserTest:
@pytest.fixture
def model_class(self):
return BaseUser
class TestAdminUser(BaseUserTest):
@pytest.fixture
def model_class(self):
return AdminUser
def test_admin_privilege(self, model_class):
user = model_class(role="super_admin")
assert user.has_privilege("all")
# 使用hypothesis生成测试数据
from hypothesis import given, strategies as st
@given(st.integers(min_value=0, max_value=150))
def test_age_validation(age):
assert 0 <= User(age=age).age <= 120
@given(st.text(min_size=1, max_size=50))
def test_username_validation(name):
if not name.isalnum():
with pytest.raises(ValidationError):
User(username=name)
else:
assert User(username=name)
def test_external_service_override():
mock_service = MockExternalService()
app.dependency_overrides[get_external_service] = lambda: mock_service
response = client.get("/data")
assert response.json() == mock_service.expected_data
app.dependency_overrides = {}
def test_error_chain():
with pytest.raises(HTTPException) as excinfo:
client.get("/error-path")
exc = excinfo.value
assert exc.status_code == 500
assert "原始错误" in exc.detail
def test_validation_error_format():
response = client.post("/users", json={"age": "invalid"})
assert response.status_code == 422
assert response.json()["detail"][0]["type"] == "type_error.integer"
def test_concurrent_requests():
with ThreadPoolExecutor() as executor:
futures = [
executor.submit(
client.get,
f"/items/{i}"
) for i in range(1000)
]
results = [f.result().status_code for f in futures]
assert all(code == 200 for code in results)
| 测试错误类型 | 解决方案 |
|---|---|
| 依赖项初始化失败 | 检查测试依赖覆盖是否正确定义 |
| 验证错误未触发 | 确认测试数据包含非法边界值 |
| 异步断言失败 | 使用pytest-asyncio管理异步测试 |
| 临时文件残留 | 使用tmp_path夹具自动清理 |

评论
发表评论