'RecursionError when mocking function by FastAPI-Testclient

I would like to test the interplay with two FastAPIs using python 3.8. In the running code, one API (main_app) is calling the other API (helper_app) by the function connect_to_helper_app. To test this without setting up two servers, I would like to use fastapi.testclient.TestClient. Unfortunately, I get a RecursionError.

To reproduce the error, you need the following files:

# Content of minimal_example/apps.py

from fastapi import FastAPI

main_app = FastAPI()
helper_app = FastAPI()


def connect_to_helper_app():
    """
    This function will be mocked in the test. In the real code, a request to the other app would be made
    """
    raise NotImplemented


@main_app.get("/call_helper")
def call_helper() -> dict:
    return connect_to_helper_app()


@helper_app.get("/")
def root() -> dict:
    return {"msg": "This is the helper app."}

And following test file:

# Content of minimal_example/test.py

from fastapi.testclient import TestClient
from minimal_example.apps import main_app, helper_app

helper_test_client = TestClient(helper_app)
main_test_client = TestClient(main_app)


def test(mocker):
    def connect_to_helper_app_with_test_client():
        result = helper_test_client.get('/')
        return result
    mocker.patch('minimal_example.apps.connect_to_helper_app', new=connect_to_helper_app_with_test_client)
    main_test_client.get('/call_helper')

The error message I get is:

test.py:7 (test)
mocker = <pytest_mock.plugin.MockerFixture object at 0x0000015DFFB93F40>

    def test(mocker):
        def connect_to_helper_app_with_test_client():
            result = helper_test_client.get('/')
            return result
        mocker.patch('minimal_example.apps.connect_to_helper_app', new=connect_to_helper_app_with_test_client)
    
>       main_test_client.get('/call_helper')

test.py:14: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\.venv\lib\site-packages\requests\sessions.py:542: in get
    return self.request('GET', url, **kwargs)
..\..\.venv\lib\site-packages\starlette\testclient.py:415: in request
    return super().request(
..\..\.venv\lib\site-packages\requests\sessions.py:529: in request
    resp = self.send(prep, **send_kwargs)
..\..\.venv\lib\site-packages\requests\sessions.py:645: in send
    r = adapter.send(request, **kwargs)
..\..\.venv\lib\site-packages\starlette\testclient.py:243: in send
    raise exc from None
..\..\.venv\lib\site-packages\starlette\testclient.py:240: in send
    loop.run_until_complete(self.app(scope, receive, send))
C:\Users\d91802\AppData\Local\Programs\Python\Python38\lib\asyncio\base_events.py:616: in run_until_complete
    return future.result()
..\..\.venv\lib\site-packages\fastapi\applications.py:208: in __call__
    await super().__call__(scope, receive, send)
..\..\.venv\lib\site-packages\starlette\applications.py:112: in __call__
    await self.middleware_stack(scope, receive, send)
..\..\.venv\lib\site-packages\starlette\middleware\errors.py:181: in __call__
    raise exc from None
..\..\.venv\lib\site-packages\starlette\middleware\errors.py:159: in __call__
    await self.app(scope, receive, _send)
..\..\.venv\lib\site-packages\starlette\exceptions.py:82: in __call__
    raise exc from None
..\..\.venv\lib\site-packages\starlette\exceptions.py:71: in __call__
    await self.app(scope, receive, sender)
..\..\.venv\lib\site-packages\starlette\routing.py:580: in __call__
    await route.handle(scope, receive, send)
..\..\.venv\lib\site-packages\starlette\routing.py:241: in handle
    await self.app(scope, receive, send)
..\..\.venv\lib\site-packages\starlette\routing.py:52: in app
    response = await func(request)
..\..\.venv\lib\site-packages\fastapi\routing.py:234: in app
    response_data = await serialize_response(
..\..\.venv\lib\site-packages\fastapi\routing.py:148: in serialize_response
    return jsonable_encoder(response_content)
..\..\.venv\lib\site-packages\fastapi\encoders.py:145: in jsonable_encoder
    return jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:93: in jsonable_encoder
    encoded_value = jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:145: in jsonable_encoder
    return jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:93: in jsonable_encoder
    encoded_value = jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:145: in jsonable_encoder
    return jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:93: in jsonable_encoder
    encoded_value = jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:145: in jsonable_encoder
    return jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:93: in jsonable_encoder
    encoded_value = jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:107: in jsonable_encoder
    jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:145: in jsonable_encoder
    return jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:93: in jsonable_encoder
    encoded_value = jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:145: in jsonable_encoder
    return jsonable_encoder(
..\..\.venv\lib\site-packages\fastapi\encoders.py:93: in jsonable_encoder
    encoded_value = jsonable_encoder(
E   RecursionError: maximum recursion depth exceeded in comparison
!!! Recursion detected (same locals & position)

In the debugger, I see that the function is sucessfully mocked by connect_to_helper_app_with_test_client and within that mocked function, the call to the helper_app returns the expected value ({"msg": "This is the helper app."}).

I would like to understand where the recursion is coming from, and how to avoid the error. Thanks in advance!



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source