在 FastAPI 中测试 Pydantic 设置

Test Pydantic settings in FastAPI

假设我的 main.py 是这样的(这是一个简化的例子,在我的应用程序中我使用了一个实际的数据库并且我有两个不同的数据库 URI 用于开发和测试):

from fastapi import FastAPI
from pydantic import BaseSettings

app = FastAPI()

class Settings(BaseSettings):
    ENVIRONMENT: str

    class Config:
        env_file = ".env"
        case_sensitive = True

settings = Settings()

databases = {
    "dev": "Development",
    "test": "Testing"
}
database = databases[settings.ENVIRONMENT]

@app.get("/")
def read_root():
    return {"Environment": database}

.env

ENVIRONMENT=dev

假设我想测试我的代码并且我想设置 ENVIRONMENT=test 以使用测试数据库。我应该怎么办?在 FastAPI 文档 (https://fastapi.tiangolo.com/advanced/settings/#settings-and-testing) 中有一个很好的例子,但它是关于依赖关系的,所以据我所知这是一个不同的例子。

我的想法如下 (test.py):

import pytest

from fastapi.testclient import TestClient

from main import app

@pytest.fixture(scope="session", autouse=True)
def test_config(monkeypatch):
    monkeypatch.setenv("ENVIRONMENT", "test")

@pytest.fixture(scope="session")
def client():
    return TestClient(app)

def test_root(client):
    response = client.get("/")
    assert response.status_code == 200
    assert response.json() == {"Environment": "Testing"}

但是没用。

此外我得到这个错误:

ScopeMismatch: You tried to access the 'function' scoped fixture 'monkeypatch' with a 'session' scoped request object, involved factories
test.py:7:  def test_config(monkeypatch)
env\lib\site-packages\_pytest\monkeypatch.py:16:  def monkeypatch()

虽然根据 pytest 官方文档,它应该可以工作 (https://docs.pytest.org/en/3.0.1/monkeypatch.html#example-setting-an-environment-variable-for-the-test-session)。我安装了最新版本的 pytest

因此我尝试使用特定的测试环境变量:https://pydantic-docs.helpmanual.io/usage/settings/#field-value-priority.

老实说我迷路了,我唯一真正的目标是拥有不同的测试配置(与 Flask 的工作方式相同:https://flask.palletsprojects.com/en/1.1.x/tutorial/tests/#setup-and-fixtures)。我是不是处理问题的方式不对?

模拟涉及 pydantic 的环境真的很棘手。

我只是在 fastapi 中通过依赖注入和制作 get_settings 函数实现了预期的行为,这本身似乎是一种很好的做法,因为即使文档也这么说。

假设你有

...

class Settings(BaseSettings):
    ENVIRONMENT: str

    class Config:
        env_file = ".env"
        case_sensitive = True

def get_settings() -> Settings:
    return Settings()

databases = {
    "dev": "Development",
    "test": "Testing"
}
database = databases[get_settings().ENVIRONMENT]

@app.get("/")
def read_root():
    return {"Environment": database}

在你的测试中你会写:

import pytest
from main import get_settings

def get_settings_override() -> Settings:
    return Settings(ENVIRONMENT="dev")

@pytest.fixture(autouse=True)
def override_settings() -> None:
    app.dependency_overrides[get_settings] = get_settings_override

如果您愿意,可以使用范围会话。

这将覆盖您的 ENVIRONMENT 变量并且不会触及其余的配置变量。

PydanticSettings 是可变的,因此您可以简单地在 test.py:

中覆盖它们
from main import settings

settings.ENVIRONMENT = 'test'

这是一种适合我的简单方法。假设您有一个名为 APPNAME.cfg 的配置文件,其中包含以下设置:

DEV_DSN='DSN=my_dev_dsn; UID=my_dev_user_id; PWD=my_dev_password'
PROD_DSN='DSN=my_prod_dsn; UID=my_prod_user_id; PWD=my_prod_password'

根据您的 OS 或 Docker 变量设置您的环境。对于 Linux,您可以输入:

export MY_ENVIORONMENT=DEV

现在考虑以下settings.py:

from pydantic import BaseSettings
import os

class Settings(BaseSettings):
    DSN: str

    class Config():
        env_prefix = f"{os.environ['MY_ENVIORONMENT']}_"
        env_file = "APPNAME.cfg"

您的应用只需执行以下操作:

from settings import Settings

s = Settings()
db = pyodbc.connect(s.DSN)