Week 14 of 16

Test the FastAPI Endpoints

Use TestClient to verify HTTP behavior without starting a real server

Day 69 60 minutes Build

Day 69 of 80

TestClient: HTTP Without a Running Server

How TestClient Works

FastAPI is built on Starlette, which provides a TestClient class. It lets you make HTTP requests directly to your FastAPI app in memory — no uvicorn, no port, no subprocess. You import your app, wrap it in TestClient, and call client.get() / client.post() as if you were making real requests.

The responses are real HTTP response objects with .status_code, .json(), and all the standard fields.

No Extra Install Needed

TestClient comes with FastAPI via Starlette. You do need httpx installed for it to work — run pip install httpx if you haven't already. FastAPI's docs mention this as a dependency for testing.

Create tests/test_api.py

tests/test_api.py python
from fastapi.testclient import TestClient
from api import app

client = TestClient(app)


def test_list_prompts():
    response = client.get("/prompts")
    assert response.status_code == 200
    assert isinstance(response.json(), list)


def test_create_prompt():
    data = {
        "platform": "Kling",
        "shot": "Test shot",
        "prompt_text": "Cinematic test"
    }
    response = client.post("/prompts", json=data)
    assert response.status_code == 201
    result = response.json()
    assert result["platform"] == "Kling"


def test_create_invalid_platform():
    data = {
        "platform": "Pika",
        "shot": "test",
        "prompt_text": "test"
    }
    response = client.post("/prompts", json=data)
    assert response.status_code == 422


def test_get_stats():
    response = client.get("/stats")
    assert response.status_code == 200
    assert isinstance(response.json(), dict)
The client is created once at module level and shared across all test functions. This is common for API tests — you're simulating a user session against the app. Notice how similar this is to testing models: assert a result, check a value.

HTTP Status Codes You'll Test Against

CodeMeaningWhen You See It
200 OK Request succeeded, data returned GET requests that find data
201 Created Resource was created Successful POST to create a prompt
404 Not Found Resource doesn't exist GET /prompts/999 when 999 doesn't exist
422 Unprocessable Entity Validation failed POST with invalid platform or missing field
500 Internal Server Error Unhandled exception in your code A bug you need to fix — never expected in tests
201 vs 200 for POST

Both work. The convention is: use 201 Created when a resource is successfully created, 200 OK when you're returning data from an existing resource. FastAPI lets you set this with @app.post("/prompts", status_code=201).

What Each Test Checks

test_list_prompts

The most basic test: call GET /prompts, get 200, get back a list. It doesn't care what's in the list — just that the endpoint exists, returns success, and returns the right data type.

test_create_prompt

POST a valid prompt, expect 201, and verify the response includes the platform we sent. This tests the full creation pipeline: request body parsing, validation, model creation, response serialization.

test_create_invalid_platform

"Pika" is not in your valid platforms list. Your API should reject it and return 422. This test verifies that your validation logic is wired into the API correctly — it's not enough for validation to exist in the model if the API doesn't surface the error properly.

test_get_stats

The stats endpoint returns a summary dictionary. This test only verifies that the endpoint works and returns a dict — not the specific counts. That's appropriate: counts depend on how many prompts exist, which varies between runs.

Inspecting Response Data

Sometimes you want to look at the full response during debugging. Add print statements temporarily:

debug technique python
def test_create_prompt():
    data = {"platform": "Kling", "shot": "Test shot", "prompt_text": "Cinematic test"}
    response = client.post("/prompts", json=data)

    # Temporarily print to inspect structure (remove before committing)
    print("\nStatus:", response.status_code)
    print("Response:", response.json())

    assert response.status_code == 201
Run with pytest tests/test_api.py::test_create_prompt -v -s — the -s flag lets print statements through. This is standard debugging technique for API tests.

Add a 404 Test

Extend test_api.py with a test for a missing resource:

tests/test_api.py — add this python
def test_get_nonexistent_prompt():
    response = client.get("/prompts/99999")
    assert response.status_code == 404


def test_delete_prompt():
    # First create a prompt to delete
    data = {"platform": "Veo", "shot": "Delete test", "prompt_text": "Will be deleted"}
    create_response = client.post("/prompts", json=data)
    assert create_response.status_code == 201

    # Then delete it
    prompt_id = create_response.json()["id"]
    delete_response = client.delete(f"/prompts/{prompt_id}")
    assert delete_response.status_code == 200
The delete test shows a common API testing pattern: create first, then operate on the created resource. You use the response from the creation to get the ID you need for the deletion.

Run All Tests Together

terminal — full test suite bash
$ pytest tests/ -v

tests/test_models.py::TestPrompt::test_create_valid_prompt PASSED
tests/test_models.py::TestPrompt::test_platform_cleaned PASSED
tests/test_models.py::TestPrompt::test_invalid_platform_raises_error PASSED
tests/test_models.py::TestPrompt::test_empty_shot_raises_error PASSED
tests/test_models.py::TestPrompt::test_str_representation PASSED
tests/test_models.py::TestPrompt::test_preview_long PASSED
tests/test_models.py::TestPrompt::test_to_dict_roundtrip PASSED
tests/test_models.py::TestPromptLibrary::test_empty_library PASSED
tests/test_models.py::TestPromptLibrary::test_add_and_retrieve PASSED
tests/test_models.py::TestPromptLibrary::test_persistence PASSED
tests/test_models.py::TestPromptLibrary::test_search PASSED
tests/test_models.py::TestPromptLibrary::test_delete PASSED
tests/test_api.py::test_list_prompts PASSED
tests/test_api.py::test_create_prompt PASSED
tests/test_api.py::test_create_invalid_platform PASSED
tests/test_api.py::test_get_stats PASSED

=================== 16 passed in 0.45s ===================
16 tests covering both your data models and your API endpoints. Every time you make a change to the project from now on, run pytest tests/ and confirm everything still passes.

End of Day Checklist

Tomorrow

Day 70 is the Week 14 review. You'll consolidate everything learned about pytest — test structure, assertions, raises, tmp_path fixtures, and TestClient — and get a preview of Week 15's CLI tools with Typer.