Search code examples
pythondependency-injectionpytestfastapi

FastAPI override Depends & Security


I have a problem with testing endpoints that use both Depends AND Security. First of all, here is my root endpoint which i can test perfectly fine using app.dependency_override:

# restapi/main.py
from api_v1.api import router as api_router
from authentication.verification import Verification
from fastapi import FastAPI, Security
from mangum import Mangum

app = FastAPI()
security = Verification()


@app.get("/")
async def root(auth_result: str = Security(security.verify)):
    return {"message": "Hello World this is restapi.py!"}


app.include_router(api_router, prefix="/api/v1")
handler = Mangum(app)

And then to test it:

from fastapi.testclient import TestClient

from restapi.main import app, security


def test_root_api():
    client = TestClient(app=app)
    app.dependency_overrides[security.verify] = lambda: ""
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World this is restapi.py!"}
    app.dependency_overrides = {}


def test_root_api_without_security():
    client = TestClient(app=app)
    response = client.get("/")
    assert response.status_code == 403
    assert response.json() == {"detail": "Not authenticated"}

There are no problems when I only use the Security function. However I have another endpoint (that i define as api/v1/exchange_rates in my api_router) which has both Depends and Security:

# restapi/api_v1/endpoints/exchange_rates.py
security = Verification()
router = APIRouter()


@router.get("/")
async def exchange_rates(
    query: ExchangeRatesQuery = Depends(),
    auth_result: str = Security(security.verify),
):

The ExchangeRatesQuery is a pydantic BaseModel. The test below for this endpoint works perfectly fine ONLY if I remove the auth_result: str = Security... from my endpoint.

from restapi.main import app
from restapi.api_v1.endpoints.exchange_rates import security

def test_exchange_rates():
    client = TestClient(app=app)
    app.dependency_overrides[security.verify] = lambda: ""
    response = client.get(
        "/api/v1/exchange-rates?start_date=2023-03-20&end_date=2023-03-21&base_currency=USD&target_currencies=EUR",
    )
    assert response.status_code == 200
    assert response.json() == [
        {
            "exchange_date": "2023-03-20",
            "exchange_rate": "0.88",
            "base_currency": "USD",
            "target_currency": "EUR",
        }
    ]
    app.dependency_overrides = {}

Even though I import security from my exchange_rates.py to ensure that I'm overriding the dependencies of the correct object, this doesn't seem to work. Any help is greatly appreciated.

Edit 1 I've also tried patching the verify function with a standard return value of "" using unittest.mock, but have not been able to make it work that way either.

@mock.patch.object(Verification, "verify", lambda: "") # also tried security instead of Verification
def test_exchange_rates()
    ...

Solution

  • app.dependency_overrides works by using the actual function instance as a key - i.e. the exact function instance that gets passed to Security or Depends.

    If these functions are not the same - either because they're registered to different instances or that they're being dynamically generated (for example by returning a function defined inside another function - usually as a closure), the dependency will not be overriden (since FastAPI has no way of knowing what function to replace the other one with).

    Make sure they're actually the same instance and same function. If you attach debug breakpoints inside the function / method being called, you should be able to see if they're the same as you expect them to be (for example by looking at self or the function being returned).