FastAPI+SQLAlchemy测试报错:实例在当前Session中不持久化
这个错误的核心原因是测试代码使用的Session与FastAPI接口处理请求时的Session是完全独立的两个实例:
- 你在测试代码中创建
Ttz实例并提交的操作,是在测试专属的AsyncSession中完成的,这个实例被该Session跟踪。 - 当通过
AsyncClient调用接口创建TtzFile时,FastAPI会通过依赖注入创建一个全新的AsyncSession来处理这个请求,接口中的数据库操作(关联Ttz与TtzFile)是在这个新Session中完成并提交的。 - 此时测试代码中的
Ttz实例仍然属于旧的测试Session,而旧Session并没有感知到新Session对数据库做出的修改。当你尝试在旧Session中调用refresh时,Session会认为这个实例已经脱离了它的管理范围(或者说该实例在当前Session中不再是持久化状态),因此抛出InvalidRequestError。
你用merge解决问题的原理是:merge方法会将外部实例(这里是测试Session中的Ttz)的状态合并到当前Session中,让当前Session重新开始跟踪这个实例,从而可以执行refresh操作。
重新查询实例:放弃使用
refresh,直接从当前测试Session中重新查询Ttz实例,这样得到的是当前Session管理的、与数据库状态同步的实例:ttz = await session.get(Ttz, ttz.id, options=[selectinload(Ttz.files)])可以直接通过
ttz.files获取关联的文件,无需额外refresh。共享Session实例:修改测试fixture,让FastAPI接口依赖的Session与测试代码使用同一个Session实例。比如在pytest中自定义Session fixture,并覆盖FastAPI的Session依赖:
@pytest.fixture async def test_session(): async with async_session() as session: yield session @pytest.fixture def client(test_session): def override_get_db(): yield test_session app.dependency_overrides[get_db] = override_get_db yield AsyncClient(app=app, base_url="http://testserver") app.dependency_overrides.clear()这样测试和接口会共用同一个Session,所有操作都在同一个Session中完成,实例始终处于被跟踪状态,不会出现
refresh报错。使用
expire强制刷新:在调用接口后,先调用session.expire(ttz)让实例的所有属性失效,再执行refresh:await session.expire(ttz) await session.refresh(ttz, attribute_names=["files"])这个方法的本质是让Session放弃当前实例的缓存状态,重新从数据库加载数据,但需要确保实例仍在Session的跟踪范围内。
统一事务上下文:如果测试使用事务回滚的fixture(比如测试结束自动回滚所有操作),可以让接口的Session加入到同一个事务中。比如在测试环境下,配置Session的事务与测试fixture的事务绑定,避免创建独立的Session,这样所有数据库操作都在同一个事务上下文里,实例始终被当前Session管理。
内容的提问来源于stack exchange,提问作者Альберт Александров

