Search code examples
pythonflaskpytestpytest-mock

Errors/exceptions during teardown of flask unit test


I am attempting to unit test some methods called by a flask application. The methods interact with a flask Response object, which I am trying to mock up for testing purposes (I don't want to test the flask Response, it works just fine.) When I try to run my tests, the tests run as expected but they throw an exception during teardown.

Example code

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

Fixture code from conftest.py

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

Test code

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


Results

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

To the best of my knowledge, running inside of the with request_context block should ensure against the "working outside of request context" error.

Am I doing something just stupid wrong here?


Solution

  • With flask 2.2.2, pytest 7.2.0 and pytest-mock 3.7.0, I cannot reproduce your issue. That being said, result.stopall() of the mocker fixture does get called after the test function ends, which also means after the test request context exits. You could try following...

    1. Have your fixture create the request context and yield nothing; the yield means that your fixture would do a teardown as well, in which it would just exit the context:
    @pytest.fixture()
    def request_context():
        with create_app().test_request_context():
            yield
    
    1. Change the order in which fixtures are called (and teared down), putting your fixture before mocker. Also, don't create another test request context inside the test because that already exists:
    def test_get_my_stuff(request_context, mocker):
        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
    
        mocker.patch("tests.app.Response", side_effect=mock_Response_get)
        response = my_func()
        assert response.status_code == 500