Testing is a crucial aspect of developing robust and reliable FastAPI applications. In this guide, we'll explore various testing strategies and techniques to ensure your FastAPI projects are thoroughly validated and perform as expected.
Before diving into testing, let's set up our environment:
pip install pytest httpx
tests
directory in your project root:mkdir tests touch tests/__init__.py
Let's start with a simple unit test for a FastAPI endpoint:
# main.py from fastapi import FastAPI app = FastAPI() @app.get("/hello/{name}") async def hello(name: str): return {"message": f"Hello, {name}!"} # tests/test_main.py from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_hello_endpoint(): response = client.get("/hello/FastAPI") assert response.status_code == 200 assert response.json() == {"message": "Hello, FastAPI!"}
Run the test using:
pytest tests/test_main.py
Integration tests help ensure different parts of your application work together seamlessly:
# dependencies.py from fastapi import Depends, HTTPException async def get_user(user_id: int): # Simulate database lookup users = {1: "Alice", 2: "Bob"} if user_id not in users: raise HTTPException(status_code=404, detail="User not found") return users[user_id] # main.py from fastapi import FastAPI, Depends from dependencies import get_user app = FastAPI() @app.get("/users/{user_id}") async def read_user(user_id: int, user: str = Depends(get_user)): return {"user_id": user_id, "username": user} # tests/test_integration.py from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_read_existing_user(): response = client.get("/users/1") assert response.status_code == 200 assert response.json() == {"user_id": 1, "username": "Alice"} def test_read_nonexistent_user(): response = client.get("/users/999") assert response.status_code == 404 assert response.json() == {"detail": "User not found"}
When testing endpoints that rely on external services, it's often necessary to mock these dependencies:
# external_service.py import httpx async def fetch_data(url: str): async with httpx.AsyncClient() as client: response = await client.get(url) return response.json() # main.py from fastapi import FastAPI from external_service import fetch_data app = FastAPI() @app.get("/fetch-external") async def fetch_external_data(): data = await fetch_data("https://api.example.com/data") return {"result": data} # tests/test_mocking.py from fastapi.testclient import TestClient from unittest.mock import patch from main import app client = TestClient(app) @patch("main.fetch_data") def test_fetch_external_data(mock_fetch_data): mock_fetch_data.return_value = {"key": "mocked_value"} response = client.get("/fetch-external") assert response.status_code == 200 assert response.json() == {"result": {"key": "mocked_value"}}
Parametrized tests allow you to run the same test with different inputs:
# main.py from fastapi import FastAPI app = FastAPI() @app.get("/multiply/{a}/{b}") async def multiply(a: int, b: int): return {"result": a * b} # tests/test_parametrized.py import pytest from fastapi.testclient import TestClient from main import app client = TestClient(app) @pytest.mark.parametrize("a,b,expected", [ (2, 3, 6), (0, 5, 0), (-1, 4, -4), (10, 10, 100) ]) def test_multiply(a, b, expected): response = client.get(f"/multiply/{a}/{b}") assert response.status_code == 200 assert response.json() == {"result": expected}
For endpoints that require authentication, you can create a test client with pre-authenticated requests:
# main.py from fastapi import FastAPI, Depends, HTTPException from fastapi.security import OAuth2PasswordBearer app = FastAPI() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def get_current_user(token: str = Depends(oauth2_scheme)): if token != "valid_token": raise HTTPException(status_code=401, detail="Invalid token") return {"username": "testuser"} @app.get("/protected") async def protected_route(current_user: dict = Depends(get_current_user)): return {"message": f"Hello, {current_user['username']}!"} # tests/test_auth.py from fastapi.testclient import TestClient from main import app client = TestClient(app) def test_protected_route_with_valid_token(): headers = {"Authorization": "Bearer valid_token"} response = client.get("/protected", headers=headers) assert response.status_code == 200 assert response.json() == {"message": "Hello, testuser!"} def test_protected_route_with_invalid_token(): headers = {"Authorization": "Bearer invalid_token"} response = client.get("/protected", headers=headers) assert response.status_code == 401 assert response.json() == {"detail": "Invalid token"}
While not strictly part of unit or integration testing, it's crucial to ensure your FastAPI application performs well under load. You can use tools like locust
for this purpose:
# locustfile.py from locust import HttpUser, task, between class FastAPIUser(HttpUser): wait_time = between(1, 3) @task def hello_endpoint(self): self.client.get("/hello/Locust") @task def multiply_endpoint(self): self.client.get("/multiply/3/7")
Run Locust with:
locust -f locustfile.py
This will start a web interface where you can configure and run your load test.
pytest.fixture
to set up and tear down test data.coverage
to ensure high test coverage of your codebase.By following these testing strategies and best practices, you'll be well on your way to developing reliable and high-quality FastAPI applications.
08/12/2024 | Python
08/11/2024 | Python
05/10/2024 | Python
22/11/2024 | Python
21/09/2024 | Python
25/09/2024 | Python
05/10/2024 | Python
05/11/2024 | Python
14/11/2024 | Python
15/10/2024 | Python
21/09/2024 | Python
15/10/2024 | Python