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
相关产品推荐
相关产品推荐

