본문 바로가기

카테고리 없음

FastAPI 연재 8편 — 테스트에서 dependency_overrides로 외부 의존성 “갈아끼우기”

반응형

FastAPI 공부하면서 내가 제일 “오… 이거 좋다” 했던 포인트가 테스트였다.

왜냐면 현실의 서버는 이런 애들이 붙어있다.

  • DB
  • 외부 HTTP API
  • AI API

근데 테스트에서 그걸 진짜로 호출하면?

  • 느림
  • flaky(가끔 실패)
  • 돈 나감(특히 AI)

그래서 테스트에서는 dependency_overrides로 의존성을 바꿔치기 한다.


1) create_app(settings)로 테스트용 앱 만들기

대표 이미지 (테스트 + dependency overrides + mock)

테스트용 DB URL을 주입해서 app를 만든다.

settings = Settings(database_url=f"sqlite:///{tmp_path / 'test.db'}")
app = create_app(settings)
client = TestClient(app)

이렇게 하면 테스트끼리 DB 파일이 분리돼서 깔끔하다.


2) dependency_overrides로 AI generator를 mock으로 바꾼다

AI 생성 테스트의 핵심은 이거.

mock_gen = AsyncMock(spec=GeminiDiaryGenerator)
mock_gen.generate = AsyncMock(return_value=_FAKE_AI_RESULT)
app.dependency_overrides[get_diary_generator] = lambda: mock_gen

이러면 실제 Gemini는 한 번도 호출되지 않는다.
근데 라우터는 “AI가 반환한 것처럼” 동작한다.

즉, 내 코드만 테스트할 수 있게 된다.


3) “실패 케이스”를 테스트로 박제하기

이게 진짜 중요하다.

  • 키 없으면 503
  • AI가 예외를 던지면 500
  • 잘못된 입력이면 422

예를 들어 “AI 키 없음”은 이렇게 테스트로 박제해둘 수 있다.

app.dependency_overrides[get_diary_generator] = lambda: None
response = client.post("/api/v1/diaries", data={"date": "2026-03-13"})
assert response.status_code == 503

그리고 “AI 내부 오류”는 이렇게.

mock_gen.generate = AsyncMock(side_effect=RuntimeError("Gemini API 오류"))
app.dependency_overrides[get_diary_generator] = lambda: mock_gen
response = client.post("/api/v1/diaries", data={"date": "2026-03-13"})
assert response.status_code == 500

이렇게 해두면, 나중에 리팩토링하다가 실수해도 테스트가 바로 잡아준다.


4) Weather도 똑같이 mock 주입한다

“외부 API를 끊고 테스트” 장면

날씨 라우터도 패턴이 동일하다.

  • get_weather_service를 override해서 mock 반환
  • 또는 None 반환시켜서 503 확인
  • 또는 ConnectError 던지게 해서 502 확인

즉, DI를 잘 설계해두면 테스트가 엄청 쉬워진다.


실습: 진짜로 pytest 한 번 돌려보기

서버 디렉토리에서:

cd apps/server
uv run pytest

테스트가 통과하면, “부트스트랩/설정/라우팅/DB/DI/검증”이 서로 잘 맞물려 있다는 증거다.


마무리

FastAPI는 “프레임워크 자체 기능”도 좋지만,
내 기준엔 테스트하기 좋은 구조로 강제되는 느낌이 제일 좋았다.

다음 편(옵션)에서는 도커/컴포즈로 로컬과 서버 실행 환경을 맞추는 얘기를 해보자.


참고

반응형