테스트에서 의존성 오버라이드하기

테스트에서 의존성 오버라이드하기 (Testing Dependencies)

테스트 중에 어떤 의존성을 **오버라이드(override)**하고 싶은 상황이 있어요. 원래 의존성(그리고 그게 가진 하위 의존성들까지)을 실행하고 싶지 않을 때죠. 대신 테스트 동안만(어쩌면 특정 테스트에서만) 쓰일 다른 의존성을 제공하고 싶은 거예요.

테스트 전용 의존성은, 원래 의존성의 값이 쓰이던 자리에서 쓸 수 있는 값을 제공해요. 이번 장에서는 FastAPI의 dependency_overrides로 이를 어떻게 하는지 배워볼게요.

출처: 공식문서

테스트 중 의존성 오버라이드하기

사용 사례: 외부 서비스

예를 들어 호출해야 하는 **외부 인증 제공자(authentication provider)**가 있다고 해 볼게요. 토큰을 보내면 인증된 사용자를 반환하는 서비스죠.

이 제공자는 요청마다 비용을 청구할 수도 있고, 호출에 시간이 걸릴 수도 있어요. 테스트에 고정된 mock 사용자를 쓰는 것보다 느리겠죠.

외부 제공자는 한 번은 테스트하고 싶겠지만, 매 테스트마다 호출하고 싶지는 않아요.

이럴 때 그 제공자를 호출하는 의존성을 오버라이드해서, 테스트에서만 mock 사용자를 반환하는 커스텀 의존성으로 바꾸면 돼요.

app.dependency_overrides 속성 사용하기

이런 상황을 위해 FastAPI 앱에는 app.dependency_overrides라는 속성이 있어요. 단순한 dict랍니다.

테스트용으로 의존성을 오버라이드하려면, 키(key)에 원래 의존성(함수)을 넣고, 값(value)에 오버라이드할 의존성(다른 함수)을 넣으면 돼요.

그러면 FastAPI가 원래 의존성 대신 그 오버라이드를 호출해요.

from typing import Annotated

from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient

app = FastAPI()


async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
    return {"message": "Hello Items!", "params": commons}


@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
    return {"message": "Hello Users!", "params": commons}


client = TestClient(app)


async def override_dependency(q: str | None = None):
    return {"q": q, "skip": 5, "limit": 10}


app.dependency_overrides[common_parameters] = override_dependency


def test_override_in_items():
    response = client.get("/items/")
    assert response.status_code == 200
    assert response.json() == {
        "message": "Hello Items!",
        "params": {"q": None, "skip": 5, "limit": 10},
    }


def test_override_in_items_with_q():
    response = client.get("/items/?q=foo")
    assert response.status_code == 200
    assert response.json() == {
        "message": "Hello Items!",
        "params": {"q": "foo", "skip": 5, "limit": 10},
    }


def test_override_in_items_with_params():
    response = client.get("/items/?q=foo&skip=100&limit=200")
    assert response.status_code == 200
    assert response.json() == {
        "message": "Hello Items!",
        "params": {"q": "foo", "skip": 5, "limit": 10},
    }

잘 보면 오버라이드된 override_dependencyskiplimit을 무시하고 고정값 5, 10을 반환해요. 그래서 클라이언트가 ?q=foo&skip=100&limit=200으로 보내도 응답의 params{"q": "foo", "skip": 5, "limit": 10}이 돼요. 이게 바로 의존성 오버라이드의 핵심 포인트예요.

FastAPI 앱 어디에서든 쓰이는 의존성에 오버라이드를 설정할 수 있어요. 원래 의존성이 경로 연산 함수, 경로 연산 데코레이터(반환 값을 쓰지 않을 때), .include_router() 호출 등 어디에 쓰이든 FastAPI는 여전히 오버라이드할 수 있어요.

그다음엔 app.dependency_overrides를 빈 dict로 설정해서 오버라이드를 제거(리셋)할 수 있어요:

app.dependency_overrides = {}

— 의존성을 특정 테스트 동안만 오버라이드하고 싶다면, 오버라이드를 테스트 함수 시작 부분(함수 안)에서 설정하고, 끝 부분(함수 끝)에서 리셋하면 돼요.

더 알아보기 (Learn more)