在FastAPI中测试Pydantic设置

16

假设我的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)。我是不是在错误的方向上解决问题?

4个回答

16

PydanticSettings 是可变的,因此您可以在您的 test.py 中简单地覆盖它们:

from main import settings

settings.ENVIRONMENT = 'test'

6
除非将 allow_mutation = False 传递给设置的 Config 对象,否则为真。 - jhrr
简单而有效! - Matteo Silvestro
1
开发人员是否应该手动将设置恢复到初始值? - evgeni tsvetanov
如果你在测试中使用这个技巧 - 不行。 - erhosen
2
使用 monkeypatch.setattr(settings, 'ENVIRONMENT', 'test') - Albert Tugushev

1
因为我找到了一个对我的用例更加简洁的解决方案,所以我要顶一下这个旧帖子。我在尝试加载特定于测试的dotenv文件时遇到了麻烦,只有在运行测试并且在项目目录中有本地开发dotenv文件时才会加载。
您可以像下面这样做,其中test.enviornment是一个特殊的dotenv文件,不是设置类Config中的env_file路径。因为环境变量>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


谢谢 - 这真的很有帮助,而且更加清晰。 - undefined

0
这是我使用的简单方法。假设你有一个名为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

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

-2

在涉及到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变量,并且不会影响其他配置变量。


感谢您的输入,但不幸的是,它似乎无论如何都不起作用。我完全按照您的指示操作。也许为了使依赖项工作,您必须使用“Depends”? 您能否提供一个最小的可行示例来测试您的建议? - Matteo Silvestro
这在某些情况下是正确的。文档忽略的是,在FastAPI中,Depends仅适用于路由(或从路由调用的依赖项),而不适用于您自己的方法。更多信息请参见[此问题](https://dev59.com/qFEG5IYBdhLWcg3wRoug)和此[问题](https://github.com/tiangolo/fastapi/issues/1693#issuecomment-665833384)。 - Chris Austin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接