在FastAPI中测试Pydantic设置



假设我的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}

而CCD_ 2是

ENVIRONMENT=dev

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

我的想法如下(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)
envlibsite-packages_pytestmonkeypatch.py:16:  def monkeypatch()

而从pytest官方文件来看,它应该可以工作(https://docs.pytest.org/en/3.0.1/monkeypatch.html#example-为测试会话设置环境变量(。我安装了最新版本的pytest

由于以下原因,我尝试使用特定的测试环境变量:https://pydantic-docs.helpmanual.io/usage/settings/#field-值优先级。

老实说,我迷失了方向,我唯一真正的目标是拥有一个不同的测试配置(就像Flask的工作方式一样:https://flask.palletsprojects.com/en/1.1.x/tutorial/tests/#setup-和固定装置(。我处理这个问题的方法不对吗?

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'

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

export MY_ENVIORONMENT=DEV

现在考虑以下设置.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)

因为我找到了一个对我的用例更干净的解决方案,所以我修改了一个旧线程。只有在测试运行时,以及在项目目录中有本地开发dotenv时,我才很难加载特定于测试的dotenv文件。

您可以执行以下操作,其中test.enviornment是一个特殊的dotenv文件,它不是设置类Config中的env_file路径。因为env-vars>对于BaseSettings的dotenv,只要它在导入设置类之前在conftest.py中运行,它就会覆盖本地.env中的任何设置。它还保证测试环境仅在运行测试时处于活动状态。

#conftest.py
from dotenv import load_dotenv
load_dotenv("tests/fixtures/test.environment", override=True)
from app import settings # singleton instance of BaseSettings class

模拟包含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

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

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

最新更新