如何在请求上下文之外工作时模拟 Flask.g 和 Flask-RESTful.request

raj*_*she 2 python unit-testing mocking flask flask-restful

我正在为一个模块编写单元测试,该模块用作多个 Flask 项目的 git 子模块。我有一个方法,Flask-RESTful.request假设它作为活动 http 请求的一部分被调用。

\n

core.helpers.auth.py

\n
from flask import g\nfrom flask_restful import request\n\nclass Auth():\n\n    @staticmethod\n    def authenticate(f):\n        def decorated(*args, **kwargs):\n            internalByPass = request.headers.get("by-pass")\n            shouldByPass = False\n            if internalByPass == "internal-bypass-key":\n                shouldByPass = True\n\n            if shouldByPass:\n                g.user = "by_pass"\n                return f(*args, **kwargs)\n\n        return decorated\n
Run Code Online (Sandbox Code Playgroud)\n

我想在测试 Auth.authenticate 方法时进行模拟。\n这是我尝试过的Flask-RESTful.request-flask.g

\n

core.tests.test_helpers.test_auth.py

\n
import pytest\nfrom core.helpers import auth\n\ndef test_authenticate(mocker):\n    mock_flask_request_obj = mocker.patch("core.helpers.auth.request")\n    mock_flask_request_obj.headers.get.return_value = "internal-bypass-key"\n\n    mock_flask_g_obj = mocker.patch("core.helpers.auth.g")\n    \n    @auth.Auth.authenticate\n    def test_func(*args, **kwargs):\n        return "hello"\n\n    assert test_func() == "hello"\n    assert mock_flask_g_obj.user == "by_pass"\n    assert mock_flask_request_obj.headers.get.assert_called_once_with("by-pass")\n
Run Code Online (Sandbox Code Playgroud)\n

我的终端出现以下错误屏幕截图

\n
(env_core_helpers_tests) \xe2\x9e\x9c  core_dome coverage run --source=./core -m pytest -v\nCoverage.py warning: --include is ignored because --source is set (include-ignored)\n=========================================================================================== test session starts ============================================================================================\nplatform darwin -- Python 3.9.6, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- /Users/rajesh_lt/workspace/core_dome/env_core_helpers_tests/bin/python3.9\ncachedir: .pytest_cache\nrootdir: /Users/rajesh_lt/workspace/core_dome\nplugins: mock-3.6.1\ncollected 1 item\n\ncore/tests/test_helpers/test_auth.py::test_authenticate FAILED                                                                                                                                       [100%]\n\n================================================================================================= FAILURES =================================================================================================\n____________________________________________________________________________________________ test_authenticate _____________________________________________________________________________________________\n\nmocker = <pytest_mock.plugin.MockerFixture object at 0x108d99160>\n\n    def test_authenticate(mocker):\n>       mock_flask_request_obj = mocker.patch.object(auth.request, mock.MagicMock())\n\ncore/tests/test_helpers/test_auth.py:28:\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\nenv_core_helpers_tests/lib/python3.9/site-packages/pytest_mock/plugin.py:219: in object\n    return self._start_patch(\nenv_core_helpers_tests/lib/python3.9/site-packages/pytest_mock/plugin.py:183: in _start_patch\n    mocked = p.start()  # type: unittest.mock.MagicMock\n/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/mock.py:1541: in start\n    result = self.__enter__()\n/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/mock.py:1405: in __enter__\n    original, local = self.get_original()\n/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/unittest/mock.py:1368: in get_original\n    original = target.__dict__[name]\nenv_core_helpers_tests/lib/python3.9/site-packages/werkzeug/local.py:422: in __get__\n    obj = instance._get_current_object()\nenv_core_helpers_tests/lib/python3.9/site-packages/werkzeug/local.py:544: in _get_current_object\n    return self.__local()  # type: ignore\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _\n\nname = \'request\'\n\n    def _lookup_req_object(name):\n        top = _request_ctx_stack.top\n        if top is None:\n>           raise RuntimeError(_request_ctx_err_msg)\nE           RuntimeError: Working outside of request context.\nE\nE           This typically means that you attempted to use functionality that needed\nE           an active HTTP request.  Consult the documentation on testing for\nE           information about how to avoid this problem.\n\nenv_core_helpers_tests/lib/python3.9/site-packages/flask/globals.py:33: RuntimeError\n========================================================================================= short test summary info ==========================================================================================\nFAILED core/tests/test_helpers/test_auth.py::test_authenticate - RuntimeError: Working outside of request context.\n============================================================================================ 1 failed in 1.62s =============================================================================================\n
Run Code Online (Sandbox Code Playgroud)\n

有没有办法在请求上下文之外进行模拟Flask-RESTful.requestflask.g如果没有,为此方法编写单元测试的正确方法是什么?

\n

raj*_*she 5

我能够使用需要 Flask 应用程序或请求上下文的测试代码找到解决方案。

更新后的代码core.tests.test_helpers.test_auth.py为:

def test_authenticate():
    with flask.Flask(__name__).test_request_context() as flask_context:
        flask_context.request.headers = {"by-pass": "internal-bypass-key"}

        @auth.Auth.authenticate
        def test_func(*args, **kwargs):
            return "hello"

        assert test_func(2, 3, 4, multiplier=2) == "hello"
        assert flask_context.g.user == "by_pass"
Run Code Online (Sandbox Code Playgroud)