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

Flask单元测试拆解阶段触发Request Context异常问题排查

问题

我正在对Flask应用的部分方法做单元测试,这些方法会和Flask Response对象交互。为了避免测试Flask Response本身(它的功能是正常的),我尝试Mock这个对象来测试。测试执行结果符合预期,但在teardown(拆解)阶段抛出了异常。

示例代码

def my_func():
    key = do_some_stuff()
    if key:
        return Response("key", status=200)
    else:
        return Response("key", status=500)

conftest.py中的Fixture代码

@pytest.fixture()
def request_context():
    app = create_app()
    return app.test_request_context()

测试代码

def test_get_my_stuff(mocker, request_context):
    def mock_Response_get(*args, **kwargs):
        class mockResponse:
            def __init__(self, status):
                self.status_code = kwargs["status"]

            def __call__(self, *args, **kwargs):
                return self.status_code

        resp = mockResponse(kwargs["status"])
        return resp


    with request_context:
        mocker.patch("my_func.Response", side_effect=mock_Response_get)
        response = my_modules.get_identity()
        assert response.status_code == 500

执行报错信息

python -m pytest

pytestconfig = <_pytest.config.Config object at 0x7fa1025054c0>

    def _mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
        """
        Return an object that has the same interface to the `mock` module, but
        takes care of automatically undoing all patches after each test method.
        """
        result = MockerFixture(pytestconfig)
        yield result
>       result.stopall()

/usr/local/lib/python3.9/site-packages/pytest_mock/plugin.py:425: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/local/lib/python3.9/site-packages/pytest_mock/plugin.py:99: in stopall
    p.stop()
/usr/local/lib/python3.9/unittest/mock.py:1553: in stop
    return self.__exit__(None, None, None)
/usr/local/lib/python3.9/unittest/mock.py:1522: in __exit__
    delattr(self.target, self.attribute)
/usr/local/lib/python3.9/site-packages/werkzeug/local.py:316: in __get__
    obj = instance._get_current_object()  # type: ignore[misc]
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def _get_current_object() -> T:
        try:
            obj = local.get()  # type: ignore[union-attr]
        except LookupError:
>           raise RuntimeError(unbound_message) from None
E           RuntimeError: Working outside of request context.
E           
E           This typically means that you attempted to use functionality that needed
E           an active HTTP request. Consult the documentation on testing for
E           information about how to avoid this problem.

/usr/local/lib/python3.9/site-packages/werkzeug/local.py:513: RuntimeError

我以为在request_context块内执行就能避免“Working outside of request context”错误,请问哪里操作错了?


问题原因与解决办法

原因分析

问题出在mock的时机和request_context的生命周期不匹配:

  • 你在with request_context:代码块内执行了mocker.patch,但pytest-mock的stopall()(撤销mock的操作)是在整个测试函数执行完毕后才运行的——这时候request_context已经退出,上下文不存在了。
  • 撤销patch时需要访问被patch的目标对象(my_func.Response),而Flask的Response是和请求上下文绑定的本地对象,此时上下文已销毁,访问它就会触发“Working outside of request context”错误。

解决办法

有两种可行的修复方式:

方式一:让mock的撤销操作在request_context内完成

把mocker.patch放在上下文管理器内,并用with语句管理patch的生命周期,确保撤销patch时上下文仍然有效:

def test_get_my_stuff(mocker, request_context):
    def mock_Response_get(*args, **kwargs):
        class mockResponse:
            def __init__(self, *args, **kwargs):
                self.status_code = kwargs["status"]

        return mockResponse(*args, **kwargs)

    with request_context:
        # 用with语句管理patch,确保撤销在上下文内执行
        with mocker.patch("my_func.Response", side_effect=mock_Response_get):
            response = my_modules.get_identity()
            assert response.status_code == 500

方式二:修改Fixture,让request_context覆盖整个测试函数的生命周期

把Fixture改成生成器,让上下文在测试函数执行期间一直保持活跃,直到测试结束才销毁:

# conftest.py
@pytest.fixture()
def request_context():
    app = create_app()
    with app.test_request_context():
        yield

然后测试代码可以简化,不需要自己写with request_context::

def test_get_my_stuff(mocker, request_context):
    def mock_Response_get(*args, **kwargs):
        class mockResponse:
            def __init__(self, *args, **kwargs):
                self.status_code = kwargs["status"]

        return mockResponse(*args, **kwargs)

    mocker.patch("my_func.Response", side_effect=mock_Response_get)
    response = my_modules.get_identity()
    assert response.status_code == 500

另外补充:你的mockResponse类里的__call__方法是多余的,因为Flask Response是实例化后直接返回对象,不需要可调用,去掉这个方法可以简化mock逻辑。


内容的提问来源于stack exchange,提问作者bcunning

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 03:25:27