はじめに
FastAPIでDBが絡むテストをしたい場合、モックを使用せずに実際のデータベースを操作することがあります。その際には、本番DBと分離したテスト用DBが必要です。
この備忘録では、テスト用DBの作成、マイグレーション、pytest設定などをまとめています。
テスト用DBの作成
DBが絡むテストをしたい場合は基本的にはプロダクト用とテスト用に使うDBは分ける
そのための手段としてテストDB用のDockerコンテナを作成
test-db:
image: postgres:15-alpine
container_name: myapp-test-db
environment:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpassword
networks:
- app-network
ports:
- "5433:5432" # ホストからアクセスする場合はポート分ける
volumes:
- test_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
基本的なフロー
- pytest実行時にDBの向き先をテストDBコンテナに向ける
- マイグレーションの実行
- マイグレーションをベースに戻す
テストケース毎にトランザクションを張ってdb操作が他のケースやスイートに影響しないようにする
pytestの設定
conftest.pyを作成して以下のfixtureと設定の追加
テスト用DBのエンジンとセッションの作成
TEST_DATABASE_URL = os.getenv(
"TEST_DATABASE_URL",
"postgresql://testuser:testpassword@test-db:5432/testdb"
)
engine = create_engine(TEST_DATABASE_URL)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Alembicマイグレーションをセッション単位で適用
@pytest.fixture(scope="session")
def apply_migrations():
"""テストDBにマイグレーションを適用"""
os.environ["DATABASE_URL"] = TEST_DATABASE_URL
alembic_path = os.path.join(os.path.dirname(__file__), "../src/alembic.ini")
alembic_cfg = Config(alembic_path)
alembic_cfg.set_main_option("sqlalchemy.url", TEST_DATABASE_URL)
# 既存のテーブルをクリーンアップしてからマイグレーション実行
command.downgrade(alembic_cfg, "base")
command.upgrade(alembic_cfg, "head")
yield
command.downgrade(alembic_cfg, "base")
DBセッション
@pytest.fixture
def db_session(apply_migrations):
"""テスト用の DB セッションを提供"""
connection = engine.connect()
transaction = connection.begin()
session = TestingSessionLocal(bind=connection)
try:
yield session
finally:
session.close()
transaction.rollback()
connection.close()
APIのDBの依存をテスト用に変更
@pytest.fixture
def client(db_session):
print("テスト用セッションs")
"""TestClient を提供し、get_db をテスト用セッションに差し替える"""
def override_get_db():
try:
yield db_session
finally:
pass # rollback は db_session fixture で処理済み
# 上書き
app.dependency_overrides[get_db] = override_get_db
from fastapi.testclient import TestClient
with TestClient(app) as c:
yield c
テストコード例
import pytest
from fastapi.testclient import TestClient
def test_create_todo(client: TestClient):
"""Todoデータ登録のテスト"""
todo_data = {
"title": "テストタスク",
"description": "これはテスト用のタスクです",
"priority": "high"
}
response = client.post("/api/v1/todos", json=todo_data)
assert response.status_code == 200
data = response.json()
assert data["title"] == todo_data["title"]
assert data["description"] == todo_data["description"]
assert data["priority"] == todo_data["priority"]
assert data["completed"] == False
assert "id" in data
assert "created_at" in data
環境変数の扱い方
pytest などでテスト DB を利用する場合、os.environ を書き換える方法があります。
import os
os.environ["DATABASE_URL"] = TEST_DATABASE_URL
環境変数はプロセス毎なので
pytest と uvicorn は別プロセスなので、pytest 内で書き換えても本番の uvicorn には影響しません。
(同一プロセス内で書き換えると、本番コードがテスト DB に接続してしまう)
補足・注意点
-
マイグレーションの戻し方
command.downgrade(alembic_cfg, "base")
をテスト終了後に実行すると、テスト用 DB のテーブルは削除されます。
テスト中にテーブルが必要な場合は、yield
の後に実行するようにしましょう。 -
環境変数の書き換え
os.environ["DATABASE_URL"] = TEST_DATABASE_URL
は同一プロセス内で書き換えると本番 DB に影響します。
pytest と uvicorn が別プロセスなら安全に使えます。 -
トランザクション管理
テストケースごとにトランザクションを張ることで、DB 操作が他のテストに影響せず、安全に並行テスト可能です。 -
Docker コンテナの分離
テスト DB と本番 DB を完全に分離することで、誤って本番 DB を操作するリスクを減らせます。 -
Alembic 設定
alembic.ini
内のscript_location
は必須です。
本番やテストでマイグレーションを実行する場合、正しいパスを指定しておく必要があります。