Skip to content

Testing FastAPI Applications

🎯 What You’ll Learn

  • The difference between unit tests and integration tests
  • How to set up pytest for FastAPI projects
  • How to use FastAPI’s TestClient to simulate requests
  • Best practices for structuring tests in production

🧠 Step 1: Unit vs Integration Tests

Type Purpose Example in FastAPI
Unit Test Test a single function or component in isolation Verify hash_password() returns a bcrypt hash
Integration Test Test multiple components working together Call /auth/login and check if JWT is returned

Both are important:

  • Unit tests catch logic errors early.
  • Integration tests ensure the whole system works correctly.

⚡ Step 2: Setting Up pytest

Install pytest:

pip install pytest

Add a tests folder at the root of your project:

...
tests/
  test_auth.py
  test_tasks.py

pytest automatically discovers files starting with test_.


⚡ Step 3: Using FastAPI’s TestClient

FastAPI provides TestClient (built on requests) to simulate API calls.

📄 tests/test_auth.py

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

# Disable rate limiting so tests don't fail on repeated runs
app.state.limiter.enabled = False

def test_signup():
    # We use a unique username to avoid "Username already exists" 400 errors
    response = client.post("/auth/signup", json={
        "username": "newuser_auth",
        "email": "auth@example.com",
        "hashed_password": "testpassword123" 
    })
    assert response.status_code == 201
    assert "user_id" in response.json()

def test_login():
    # 1. Ensure user exists
    client.post("/auth/signup", json={
        "username": "loginuser",
        "email": "login@example.com",
        "hashed_password": "testpassword123"
    })

    # 2. Login using FORM DATA (required by OAuth2PasswordRequestForm)
    response = client.post("/auth/login", data={
        "username": "loginuser",
        "password": "testpassword123"
    })

    assert response.status_code == 200
    data = response.json()
    assert "access_token" in data
    assert data["token_type"] == "bearer"

🔍 What’s happening?

  • TestClient(app) spins up the FastAPI app in memory.
  • client.post() simulates HTTP requests.
  • Assertions check status codes and response data.

⚡ Step 4: Integration Test Example

📄 tests/test_tasks.py

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)
app.state.limiter.enabled = False

def test_task_lifecycle():
    # 1. Setup: Create and Login
    username = "taskuser"
    password = "taskpassword"
    client.post("/auth/signup", json={
        "username": username,
        "email": "tasks@example.com",
        "hashed_password": password
    })

    login_res = client.post("/auth/login", data={
        "username": username,
        "password": password
    })
    token = login_res.json()["access_token"]
    headers = {"Authorization": f"Bearer {token}"}

    # 2. Create Task
    create_res = client.post(
        "/tasks/", 
        json={"title": "Finish API Tests", "description": "Ensure everything passes"},
        headers=headers
    )
    assert create_res.status_code == 201
    task_id = create_res.json()["id"]

    # 3. Get Tasks
    get_res = client.get("/tasks/", headers=headers)
    assert get_res.status_code == 200
    assert any(t["id"] == task_id for t in get_res.json())

    # 4. Update Task
    update_res = client.put(
        f"/tasks/{task_id}",
        json={"title": "Updated Title", "completed": True},
        headers=headers
    )
    assert update_res.status_code == 200
    assert update_res.json()["title"] == "Updated Title"

    # 5. Delete Task
    del_res = client.delete(f"/tasks/{task_id}", headers=headers)
    assert del_res.status_code == 204

🔍 Why this matters?

  • Tests the full flow: login → create task → fetch tasks.
  • Ensures JWT authentication and task ownership work together.

⚡ Step 5: Running Tests

Run all tests:

python -m pytest

Run a specific file:

python -m pytest tests/test_auth.py

Run with verbose output:

python -m pytest -v

🧠 Best Practices

  • Separate unit tests (pure functions) from integration tests (API endpoints).
  • Use fixtures for reusable setup (e.g. test database, test users).
  • Mock external services (e.g. email, payment APIs) to avoid slow tests.
  • Run tests in CI/CD pipelines before deployment.

🧪 Practice Challenge

  1. Write a unit test for verify_password() to ensure it rejects wrong passwords.
  2. Write an integration test for /admin/users that:
    • Logs in as a regular user → expects 403 Forbidden.
    • Logs in as an admin → expects a list of users.
  3. Add a fixture that sets up a temporary in-memory SQLite database for tests.

🧠 Recap

You now know how to:

  • Write unit and integration tests for FastAPI apps
  • Use pytest and TestClient to simulate requests
  • Structure tests for maintainability and production readiness