Belajar FastAPI - Testing
Episode 10 of 28

Belajar FastAPI - Testing

Menguji aplikasi FastAPI dengan TestClient (httpx) dan pytest: fixture database terisolasi, dependency override untuk memalsukan database, monkeypatch untuk mengganti perilaku, dan pola test untuk CRUD lengkap.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 9 kita membangun CRUD lengkap dengan SQLAlchemy, sekarang kita buktikan bahwa aplikasi itu benar-benar bekerja — dengan testing. Banyak developer melewati tahap ini demi kecepatan, padahal justru di sinilah perbedaan antara "jalan di laptop" dan "bisa dideploy dengan percaya diri".

Mengapa episode ini penting? Karena FastAPI memiliki salah satu keunggulan testing terbaik di dunia web Python: dependency override. Logika yang di episode 9 bergantung pada database bisa diganti total di dalam test tanpa mengubah satu baris kode aplikasi. Inilah arsitektur DI (episode 8) yang mulai terbayar.

Install Alat Testing

Install pytest dan httpx
pip install pytest httpx

FastAPI 0.141 menyediakan TestClient yang dibangun di atas httpx — klien HTTP yang bisa menjalankan aplikasi FastAPI secara in-memory, tanpa server sungguhan.

TestClient Pertama

Pythontests/test_main.py
from fastapi.testclient import TestClient
 
from app.main import app
 
client = TestClient(app)
 
 
def test_read_root() -> None:
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Halo Dunia FastAPI"}

TestClient(app) membungkus aplikasi dan mengirim request langsung lewat ASGI — response-nya objek httpx dengan .status_code, .json(), dan .headers. Uji berjalan tanpa membuka port, jadi cepat dan aman dijalankan di CI.

Tip

TestClient juga mengeksekusi seluruh pipeline — dependency, middleware, dan validasi. Jadi test GET / di atas sudah memverifikasi hot path FastAPI, bukan hanya fungsi handler.

Fixture Database Terisolasi

Test tidak boleh mengotori database sungguhan. Pola klasik: pakai SQLite in-memory yang dibuat ulang per test:

Pythontests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
 
from app.database import Base, get_db
from app.main import app
from fastapi.testclient import TestClient
 
 
@pytest.fixture()
def db_session():
    engine = create_engine(
        "sqlite://", connect_args={"check_same_thread": False}
    )
    Base.metadata.create_all(engine)
    TestingSession = sessionmaker(bind=engine)
    session = TestingSession()
    try:
        yield session
    finally:
        session.close()
        Base.metadata.drop_all(engine)
 
 
@pytest.fixture()
def client(db_session):
    def override_get_db():
        yield db_session
 
    app.dependency_overrides[get_db] = override_get_db
    yield TestClient(app)
    app.dependency_overrides.clear()

Baris paling penting: app.dependency_overrides[get_db] = override_get_db. Ini memberitahu FastAPI: untuk fungsi dependency get_db, pakai versi ini — sesi test — bukan yang asli. Semua handler di episode 9 yang memakai Depends(get_db) otomatis terhubung ke database test.

Important

dependency_overrides adalah peta dari fungsi dependency asli ke penggantinya, dan harus dibersihkan setelah test (app.dependency_overrides.clear()) agar tidak bocor ke test berikutnya. Inilah mengapa override di-fixture, bukan di dalam fungsi test.

Test CRUD Lengkap

Pythontests/test_items.py
def test_create_item(client) -> None:
    response = client.post(
        "/items/",
        json={"name": "Keyboard", "price": 500000},
    )
    assert response.status_code == 201
    body = response.json()
    assert body["name"] == "Keyboard"
    assert body["id"] == 1
 
 
def test_read_missing_item_returns_404(client) -> None:
    response = client.get("/items/999")
    assert response.status_code == 404
 
 
def test_create_item_with_bad_price(client) -> None:
    response = client.post(
        "/items/",
        json={"name": "Keyboard", "price": -5},
    )
    assert response.status_code == 422

Tiga test ini mencakup tiga jalur berbeda: sukses (201), tidak ketemu (404), dan validasi gagal (422). Perhatikan bahwa kita tidak menebak status code — FastAPI dan kode kita sendiri yang menentukan, dan test mengunci perilaku itu agar tidak berubah diam-diam.

Monkeypatch: Mengganti Perilaku di Runtime

Terkadang perlu mengganti fungsi tertentu — misalnya memalsukan waktu atau klien eksternal — tanpa menyentuh database. monkeypatch pytest adalah alatnya:

PythonContoh monkeypatch
import time
 
 
def _palsu_waktu():
    return 1_700_000_000
 
 
def test_timestamp(monkeypatch, client) -> None:
    monkeypatch.setattr(time, "time", _palsu_waktu)
    response = client.get("/items/")
    assert response.status_code == 200

monkeypatch.setattr(time, "time", _palsu_waktu) menimpa time.time selama test berjalan, lalu memulihkannya otomatis setelah selesai — tanpa efek samping ke test lain. Ini pola utama untuk menguji kode yang bergantung pada jam, random, atau API eksternal.

Organisasi File Test

Struktur folder test
fastapi-lab/
  app/
    main.py
    database.py
    models.py
    schemas.py
    deps.py
  tests/
    conftest.py
    test_main.py
    test_items.py

Konvensi pytest: file conftest.py berisi fixture bersama, file test_*.py berisi test. Jalankan seluruh suite:

Jalankan test
pytest -v

Output harus menunjukkan semua test hijau. Di CI (episode 24), perintah ini menjadi gerbang sebelum deployment.

Warning

Jangan menulis test yang membaca database sungguhan. Test harus deterministik — hasilnya sama di laptop, CI, dan produksi. SQLite in-memory per-fixture di atas menjamin itu. Test yang bergantung environment hanya akan membuang waktu kalian saat berantakan.

Coverage dan Kebiasaan

PraktikAlasan
Test per status codeMengunci kontrak API
Fixture client dan db_sessionIsolasi dan kecepatan
dependency_overridesMemalsukan resource tanpa refactor
Jalankan di CIMencegah regresi di setiap commit

Penutup

Inti yang harus dibawa pulang:

  • TestClient = httpx in-memory; cepat, tanpa port, mengeksekusi pipeline penuh.
  • Fixture database per-test dengan SQLite in-memory menjaga isolasi.
  • app.dependency_overrides[get_db] menggantikan sesi asli dengan sesi test — kekuatan DI.
  • monkeypatch untuk mengganti fungsi runtime.
  • Test CRUD mengunci status code: 201, 404, 422.

Di episode 11 selanjutnya kita akan membahas middleware & CORS — menyisipkan logika di level HTTP sebelum dan sesudah routing, mengkonfigurasi CORS agar API bisa dipakai browser, serta membatasi host yang sah dengan TrustedHost. Setelah ini, API kalian siap "bergaul" dengan frontend!

Belajar FastAPI - Testing | Belajar FastAPI