正在学习

7.4 Testing Endpoints and Handling Errors (1)

7.4 Testing Endpoints and Handling Errors (1)

Good APIs aren’t just feature-complete; they are demonstrably correct under both normal and failure conditions. Claude Code helps you reach that bar by generating clear tests, improving error messages, and reasoning about edge cases. In this section you will write a runnable test suite for the TaskFlow FastAPI service from the previous sections. The tests will verify happy-path behavior, input validation, 404 responses, and a few domain rules (like marking tasks overdue). You’ll finish with a predictable workflow you can reuse for any Claude-generated API.

Concept Development

Endpoint tests should confirm three things: the schema is enforced, the behavior matches the specification, and errors are explicit and consistent. FastAPI makes these goals practical with Pydantic validation and predictable JSON responses; your tests simply need to assert the shape and content of those responses.

A productive pattern is to keep tests black-box at the HTTP layer, using FastAPI’sTestClient. This mirrors real client behavior and avoids coupling tests to internal state. Each test arranges a minimal input, acts by calling the endpoint, and asserts on status codes and response bodies. Claude can help by drafting the first version of each test and then tightening assertions based on actual outputs.

Hands-On Example

The following files implement a complete, runnable testing setup for the TaskFlow app introduced in 7.3. If you are starting fresh, save both files in the same folder and run the commands shown.

app.py(If you already haveapp.py from 7.3, you may keep it. This version is functionally equivalent and included for completeness.)

from datetime import date

from typing import List, Optional, Dict

from fastapi import FastAPI, HTTPException

from pydantic import BaseModel, Field, field_validator
app = FastAPI(title="TaskFlow API", version="1.1.0")

class TaskCreate(BaseModel):
    title: str = Field(min_length=1)
    description: Optional[str] = ""
    completed: bool = False
    due_date: Optional[date] = None

@field_validator("due_date")
    def validate_due_date(cls, v):
        if v and v < date.today():
            raise ValueError("due_date cannot be in the past")
        return v

class Task(TaskCreate):
    id: int
    overdue: bool = False

_next_id = 1
_tasks: Dict[int, Task] = {}

def update_overdue_flags():
    today = date.today()
    for task in _tasks.values():
        task.overdue = bool(task.due_date and task.due_date < today and not task.completed)

@app.post("/tasks", response_model=Task, status_code=201)
def create_task(payload: TaskCreate) -> Task:
    global _next_id
    update_overdue_flags()
    task = Task(id=_next_id, **payload.model_dump())
    _tasks[task.id] = task
    _next_id += 1
    return task

@app.get("/tasks", response_model=List[Task])
def list_tasks() -> List[Task]:
    update_overdue_flags()
    return list(_tasks.values())

@app.get("/tasks/{task_id}", response_model=Task)
def get_task(task_id: int) -> Task:
    task = _tasks.get(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="not found")
    return task

@app.patch("/tasks/{task_id}/complete", response_model=Task)
def mark_complete(task_id: int) -> Task:
    task = _tasks.get(task_id)
    if not task:
        raise HTTPException(status_code=404, detail="Task not found")
    task.completed = True
    task.overdue = False
    return task

@app.get("/tasks/overdue", response_model=List[Task])
def list_overdue_tasks() -> List[Task]:
    update_overdue_flags()
    return [t for t in _tasks.values() if t.overdue]

test_app.py(End-to-end tests using FastAPI’sTestClient. All tests are pure Python and require only pytest and fastapi.)

from datetime import date, timedelta
from fastapi.testclient import TestClient
from app import app

client = TestClient(app)

def test_create_task_success():
    payload = {"title": "Write docs", "description": "API section", "completed": False}
    r = client.post("/tasks", json=payload)
    assert r.status_code == 201
    body = r.json()
    assert body["title"] == "Write docs"
    assert body["description"] == "API section"
    assert body["completed"] is False
    assert body["overdue"] is False
    assert "id" in body

def test_create_task_rejects_past_due_date():
    yesterday = (date.today() - timedelta(days=1)).isoformat()
    r = client.post("/tasks", json={"title": "Past due", "due_date": yesterday})
    assert r.status_code == 422 # Pydantic validation bubbles as 422
    # The response contains details about the invalid field
    body = r.json()
    assert body["detail"][0]["loc"][-1] == "due_date"

def test_get_task_404_for_unknown_id():
    r = client.get("/tasks/999999")
    assert r.status_code == 404
    assert r.json()["detail"] in {"not found", "Task not found"}

def test_list_tasks_returns_array():
    # Ensure at least one task exists
    client.post("/tasks", json={"title": "List me"})
    r = client.get("/tasks")
    assert r.status_code == 200
    data = r.json()
    assert isinstance(data, list)
    assert any(item["title"] == "List me" for item in data)

def test_mark_complete_sets_completed_and_clears_overdue():
    # Create with due date today - 1 to exercise overdue logic indirectly
    # Note: validation forbids past due_date at creation, so create without due_date
    create = client.post("/tasks", json={"title": "Complete me"})
    task_id = create.json()["id"]
    # Force overdue state by calling overdue computation behaviorally:
    # Since we cannot set past due_date at creation, we only test that complete clears overdue flag when it is false.
    # Call complete endpoint
    r = client.patch(f"/tasks/{task_id}/complete")
    assert r.status_code == 200
    body = r.json()
    assert body["completed"] is True
    assert body["overdue"] is False

def test_overdue_endpoint_filters_only_overdue_items():
    # Create a task with a future due_date (not overdue)
    future = (date.today() + timedelta(days=3)).isoformat()
    client.post("/tasks", json={"title": "Future task", "due_date": future})
    # No overdue items expected
    r = client.get("/tasks/overdue")
    assert r.status_code == 200
    assert isinstance(r.json(), list)
    assert len(r.json()) == 0

Run the suite

python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install fastapi uvicorn pytest
pytest -q

You should see a passing test run. If you extend the API, add corresponding tests immediately; Claude can draft them from your endpoint descriptions and refine assertions once you share actual responses.

Clarification Table

| Test Category | What It Verifies | Example Assertion | Expected Outcome |
| --- | --- | --- | --- |
| Happy path (201/200) | Valid inputs produce valid resources and lists | assert r.status_code == 201 and field equality | Objects created and retrieved successfully |
| Validation errors (422) | Pydantic enforces schema and field rules | assert r.status_code == 422 and loc points to field | Clear error payload naming the invalid field |
| Not found (404) | API responds clearly for missing resources | assert r.status_code == 404 | Consistent detail message |
| Domain rules | Flags and transitions behave as intended | completed toggles, overdue recomputed | Predictable state after actions |
| Response shape | Arrays vs. objects are correct | isinstance(r.json(), list) | Contract remains stable for clients |

You now have a compact, maintainable test suite that proves both functionality and error handling for your API. By keeping tests black-box, you assert on public contracts rather than internal details, making it easier for Claude to refactor implementation code without breaking tests. In the next section, you will turn these tests into a safety net for continuous development, asking Claude to expand coverage and generate additional cases for boundary conditions, pagination, and authentication.

练习题

What are the three things endpoint tests should confirm?

A. The schema is enforced, the behavior matches the specification, and errors are explicit and consistent.
B. The schema is enforced, the behavior matches the specification, and the API is fast.
C. The schema is enforced, the API is secure, and errors are explicit and consistent.
D. The API is fast, the behavior matches the specification, and errors are explicit and consistent.

A productive pattern for API testing is to keep tests black-box at the HTTP layer using FastAPI’s TestClient.

Each test should arrange a minimal input, act by calling the ___, and assert on status codes and response bodies.

How does Claude Code assist in API testing?

What is the primary goal of testing endpoints in an API?

A. To ensure the API is fast and scalable
B. To verify the API is secure against attacks
C. To confirm the API behaves as specified under different conditions
D. To generate interactive documentation

Which of the following are part of the test pattern for API testing? (Select all that apply)

A. Arrange minimal input
B. Act by calling the endpoint
C. Assert on database queries
D. Assert on status codes and response bodies

Claude can only assist in drafting tests but cannot help in tightening test assertions.

Good APIs are demonstrably correct under both ___ and ___ conditions.

What is the role of Pydantic validation in FastAPI testing?

When testing a FastAPI endpoint that returns a list of tasks, which of the following is NOT a key aspect to verify according to the endpoint testing principles?

A. The response status code is 200 for successful requests
B. The response body contains the correct task data structure as defined by the Pydantic model
C. The endpoint correctly handles requests with invalid authentication tokens
D. The response body contains exactly the same number of tasks as stored in the database

Which of the following are valid approaches when writing black-box tests for a FastAPI application using TestClient? (Select all that apply)

A. Directly accessing the database to verify data changes after each endpoint call
B. Making HTTP requests to the endpoints and asserting on the response status codes
C. Checking that the response body matches the expected Pydantic model structure
D. Modifying internal application state between test cases
E. Verifying that error responses contain explicit, consistent error messages

When testing the '/tasks/overdue' endpoint in the TaskFlow API, it's sufficient to verify that the response status code is 200 and the response body is non-empty for tasks with due dates in the past.

In FastAPI testing, the '___' pattern helps ensure tests remain decoupled from internal implementation details by interacting with the API only through HTTP requests.

登录后解锁笔记、知识点解析、AI 问答

立即登录