Testing ด้วย TestClient และ pytest
supertest / Jest → TestClient + pytest
หัวข้อที่มีชื่อว่า “supertest / Jest → 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
// Jest + supertest — app.test.tsimport 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); });});# pytest + TestClient — test_items.pyfrom fastapi.testclient import TestClientfrom 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 == 404Testing POST พร้อม request body
หัวข้อที่มีชื่อว่า “Testing POST พร้อม request body”// supertest POSTit('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);});# TestClient POSTdef 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 == 422Override dependencies ใน tests
หัวข้อที่มีชื่อว่า “Override dependencies ใน tests”pattern การ testing ที่สำคัญใน FastAPI คือ app.dependency_overrides ซึ่งแทนที่ dependencies จริง (เช่น database session) ด้วย test doubles เทียบเท่ากับ jest.mock() หรือ overrideProvider() ของ NestJS
// NestJS — override provider ใน testsconst moduleRef = await Test.createTestingModule({ providers: [ItemsService],}).overrideProvider(DatabaseService).useValue({ findById: jest.fn().mockResolvedValue({ id: 1 }) }).compile();# FastAPI — dependency_overridesfrom fastapi.testclient import TestClientfrom app.main import appfrom app.database import get_db
# Fake DB session สำหรับ testsdef 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 fixtures สำหรับ setup/teardown
หัวข้อที่มีชื่อว่า “pytest fixtures สำหรับ setup/teardown”@pytest.fixture ของ pytest เทียบเท่ากับ beforeEach / afterEach ของ Jest ไฟล์ conftest.py แชร์ fixtures ข้าม test files เหมือน Jest global setup file
// Jest beforeEach / afterEachlet client: TestClient;
beforeAll(async () => { const app = await NestFactory.create(AppModule); await app.init(); client = app.getHttpServer();});
afterAll(async () => { await app.close(); });# pytest — conftest.py fixturesimport pytestfrom fastapi.testclient import TestClientfrom app.main import appfrom app.database import Base, engine, get_dbfrom sqlalchemy import create_enginefrom 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-covPlayground ข้ามในบทเรียนนี้: TestClient ต้องการ FastAPI app จริงและ HTTP lifecycle