如何用带参数的Fixture覆写FastAPI的app.dependency_overrides?
问题解决:让TestClient使用可回滚的SQLAlchemy Session进行集成测试
问题背景
我基于SQLAlchemy搭建了测试用的数据库连接Fixture,核心特性是会回滚事务(测试不会修改数据库):
@pytest.fixture(scope="session") def connection(): SQLALCHEMY_DATABASE_URL = "postgresql://{0}:{1}@{2}:{3}/{4}".format( settings.DB_username, settings.DB_password, settings.DB_hostname, settings.DB_port, settings.DB_name) engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"options": "-c timezone=utc"} ) return engine.connect() @pytest.fixture(scope="session") def db_session(connection): transaction = connection.begin() yield scoped_session( sessionmaker(autocommit=False, autoflush=False, bind=connection) ) transaction.rollback()
同时我有一个提供测试客户端的Fixture,用于从API到数据库的集成测试:
@pytest.fixture(scope='module') def client() -> Generator: with TestClient(app) as c: yield c
目前一切正常,但问题在于应用及所有接口通过db: Session = Depends(get_db)注入数据库,客户端使用的是不会回滚的“真实”连接,导致数据库修改被持久化。
我尝试覆写get_db:
@pytest.fixture(scope='module') def client() -> Generator: app.dependency_overrides[get_db] = db_session with TestClient(app) as c: yield c
但出现422错误,原因是我的配置中db_session所需的参数缺失。移除覆写语句后测试恢复正常。
请问如何用db_session(connection)覆写get_db,或如何让TestClient与其他测试使用同一个可回滚的db_session?
解决方案
问题出在直接把db_session这个fixture函数赋值给dependency_overrides,FastAPI的依赖项需要的是一个无参数的可调用对象,而db_session需要依赖connection参数,直接赋值会导致参数缺失报错。
你需要先获取已经实例化的db_session对象,再用一个无参数包装函数返回它,然后把这个函数作为依赖项覆写:
修改后的client fixture
@pytest.fixture(scope='module') def client(db_session) -> Generator: # 定义无参数的生成器函数,返回已初始化的可回滚session def override_get_db(): try: yield db_session finally: pass # session的回滚由外层db_session fixture处理,无需手动关闭 app.dependency_overrides[get_db] = override_get_db with TestClient(app) as c: yield c # 测试结束后清除依赖覆写,避免影响其他测试用例 app.dependency_overrides.clear()
核心逻辑说明
clientfixture现在依赖db_session,pytest会自动完成connection和db_session的初始化流程,拿到已创建好的scoped session实例override_get_db是符合FastAPI依赖要求的无参数生成器,直接返回我们的可回滚session- 测试结束后调用
app.dependency_overrides.clear(),避免该覆写影响其他测试用例
额外注意事项
- 确保你的原
get_db函数也是生成器形式(通常项目中都是这样实现):def get_db(): db = SessionLocal() try: yield db finally: db.close() - 如果需要更细粒度的测试隔离(比如每个测试用例单独回滚),可以把
db_session和client的scope改为function;session级别的scope在多数集成测试中性能更优,且隔离性足够
内容的提问来源于stack exchange,提问作者dermoritz
相关产品推荐
相关产品推荐

