FastApi-Testclient嘲笑功能时递归eRROR

发布于 2025-01-31 21:01:34 字数 5653 浏览 3 评论 0原文

我想使用python 3.8测试与两个Fastapis的相互作用。在运行代码中,一个API(main_app)通过函数connect_to_to_helper_app调用另一个API(Helper_app)。要在不设置两个服务器的情况下进行测试,我想使用fastapi.testclient.testclient。不幸的是,我得到了recursionError

要重现错误,您需要以下文件:

# 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."}

以及以下测试文件:

# 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')

我收到的错误消息是:

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)

在调试器中,我看到该函数是由connect_to_to_helper_with_with_with_with_test_client connect_to_to_to_helper_with_with_with_with_with_with 对helper_app的调用返回预期值({“ msg”:“这是辅助应用程序。”})。

我想了解递归的来源以及如何避免错误。提前致谢!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

亚希 2025-02-07 21:01:34

我认为您应该将connect_to_helper_app()将其拉到单独的类中。这样,您可以嘲笑导入而不是实际实现。

# Content of minimal_example/connection.py
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

# Content of minimal_example/apps.py

from fastapi import FastAPI
from minimal_example.connection import connect_to_helper_app

main_app = FastAPI()
helper_app = FastAPI()


@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."}
# 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')

I think you should pull your connect_to_helper_app() into a separate class. This way you can mock the import instead of the actual implementation.

# Content of minimal_example/connection.py
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

# Content of minimal_example/apps.py

from fastapi import FastAPI
from minimal_example.connection import connect_to_helper_app

main_app = FastAPI()
helper_app = FastAPI()


@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."}
# 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')
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文