ข้ามไปยังเนื้อหา

Testing ด้วย TestClient และ pytest

ใน Node คุณใช้ supertest ทำ HTTP requests กับ Express หรือ NestJS app ใน process และ Jest เป็น test runner ใน FastAPI คุณใช้ TestClient (จาก Starlette, bundled กับ FastAPI) สำหรับ in-process HTTP requests และ pytest เป็น test runner

TypeScript
// Jest + supertest — app.test.ts
import request from 'supertest';
import { app } from '../src/app';
describe('GET /items/:id', () => {
it('returns an item', async () => {
const res = await request(app).get('/items/1');
expect(res.status).toBe(200);
expect(res.body.id).toBe(1);
});
it('returns 404 for unknown id', async () => {
const res = await request(app).get('/items/9999');
expect(res.status).toBe(404);
});
});
Python
# pytest + TestClient — test_items.py
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_get_item():
response = client.get("/items/1")
assert response.status_code == 200
assert response.json()["id"] == 1
def test_get_item_not_found():
response = client.get("/items/9999")
assert response.status_code == 404
TypeScript
// supertest POST
it('creates an item', async () => {
const res = await request(app)
.post('/items')
.send({ name: 'Widget', price: 9.99 })
.set('Content-Type', 'application/json');
expect(res.status).toBe(201);
expect(res.body.name).toBe('Widget');
});
it('rejects invalid input', async () => {
const res = await request(app)
.post('/items')
.send({ name: '', price: -1 });
expect(res.status).toBe(422);
});
Python
# TestClient POST
def test_create_item():
response = client.post(
"/items",
json={"name": "Widget", "price": 9.99},
)
assert response.status_code == 201
assert response.json()["name"] == "Widget"
def test_create_item_invalid():
response = client.post(
"/items",
json={"name": "", "price": -1},
)
assert response.status_code == 422

pattern การ testing ที่สำคัญใน FastAPI คือ app.dependency_overrides ซึ่งแทนที่ dependencies จริง (เช่น database session) ด้วย test doubles เทียบเท่ากับ jest.mock() หรือ overrideProvider() ของ NestJS

TypeScript
// NestJS — override provider ใน tests
const moduleRef = await Test.createTestingModule({
providers: [ItemsService],
})
.overrideProvider(DatabaseService)
.useValue({ findById: jest.fn().mockResolvedValue({ id: 1 }) })
.compile();
Python
# FastAPI — dependency_overrides
from fastapi.testclient import TestClient
from app.main import app
from app.database import get_db
# Fake DB session สำหรับ tests
def override_get_db():
yield FakeDBSession()
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)
def test_get_item_with_fake_db():
response = client.get("/items/1")
assert response.status_code == 200
# Restore หลัง tests (หรือใช้ pytest fixture)
app.dependency_overrides.clear()

@pytest.fixture ของ pytest เทียบเท่ากับ beforeEach / afterEach ของ Jest ไฟล์ conftest.py แชร์ fixtures ข้าม test files เหมือน Jest global setup file

TypeScript
// Jest beforeEach / afterEach
let client: TestClient;
beforeAll(async () => {
const app = await NestFactory.create(AppModule);
await app.init();
client = app.getHttpServer();
});
afterAll(async () => { await app.close(); });
Python
# pytest — conftest.py fixtures
import pytest
from fastapi.testclient import TestClient
from app.main import app
from app.database import Base, engine, get_db
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
TEST_DB_URL = "sqlite:///./test.db"
test_engine = create_engine(TEST_DB_URL, connect_args={"check_same_thread": False})
TestSession = sessionmaker(bind=test_engine)
@pytest.fixture(scope="module")
def client():
Base.metadata.create_all(bind=test_engine)
def override_db():
db = TestSession()
try:
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_db
with TestClient(app) as c:
yield c
Base.metadata.drop_all(bind=test_engine)

รันที่เครื่องตัวเอง — ต้องการ FastAPI + httpx + pytest ติดตั้ง: pip install httpx pytest pytest-cov Playground ข้ามในบทเรียนนี้: TestClient ต้องการ FastAPI app จริงและ HTTP lifecycle

FastAPI equivalent ของ supertest npm package สำหรับ in-process HTTP testing คืออะไร?
จะแทนที่ real database session ด้วย fake ใน FastAPI tests ได้อย่างไร?
ไฟล์ใดที่แชร์ pytest fixtures ข้าม test files ทั้งหมดในไดเรกทอรี?