You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,提问作者Альберт Александров

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:26:13