Saltar al contenido

Testing de Dependencias con Overrides

Sobrescribir dependencias durante el testing

Hay algunos escenarios donde podrías querer sobrescribir una dependencia durante el testing.

No quieres que la dependencia original se ejecute (ni ninguna de las sub-dependencias que pueda tener).

En su lugar, quieres proporcionar una dependencia diferente que será usada solo durante las pruebas (posiblemente solo algunas pruebas específicas), y proporcionará un valor que puede ser usado donde se usaba el valor de la dependencia original.

Casos de uso: servicio externo

Un ejemplo podría ser que tienes un proveedor de autenticación externo al que necesitas llamar.

Le envías un token y devuelve un usuario autenticado.

Este proveedor podría cobrarte por petición, y llamarlo podría tomar algo de tiempo extra comparado con tener un usuario mock fijo para las pruebas.

Probablemente quieras probar el proveedor externo una vez, pero no necesariamente llamarlo para cada prueba que se ejecuta.

En este caso, puedes sobrescribir la dependencia que llama a ese proveedor, y usar una dependencia personalizada que devuelva un usuario mock, solo para tus pruebas.

Usar el atributo app.dependency_overrides

Para estos casos, tu aplicación FastAPI tiene un atributo app.dependency_overrides, es un simple dict.

Para sobrescribir una dependencia para testing, pones como clave la dependencia original (una función), y como valor, tu override de dependencia (otra función).

Y entonces FastAPI llamará a ese override en lugar de la dependencia original.

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},
    }
🤓 Otras versiones y variantes

Consejo

Preferible usar la versión con Annotated si es posible.

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: dict = Depends(common_parameters)):
    return {"message": "Hello Items!", "params": commons}


@app.get("/users/")
async def read_users(commons: 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},
    }

Consejo

Puedes establecer un override de dependencia para una dependencia usada en cualquier parte de tu aplicación FastAPI.

La dependencia original podría ser usada en una función path operation, un decorador path operation (cuando no usas el valor de retorno), una llamada a .include_router(), etc.

FastAPI aún podrá sobrescribirla.

Luego puedes restablecer tus overrides (eliminarlos) estableciendo app.dependency_overrides como un dict vacío:

app.dependency_overrides = {}

Consejo

Si quieres sobrescribir una dependencia solo durante algunas pruebas, puedes establecer el override al principio de la prueba (dentro de la función de prueba) y restablecerlo al final (al final de la función de prueba).