简体   繁体   English

使用 TortoiseOrm 作为数据库层测试 FastApi 应用程序的问题

[英]Issues testing a FastApi app using TortoiseOrm as a database layer

I am having issues with testing a FastApi app using TortoiseOrm as a database layer.我在使用 TortoiseOrm 作为数据库层测试 FastApi 应用程序时遇到问题。 Please find below a minimal reproducible example.请在下面找到一个最小的可重现示例。 The example is a small API with 2 endpoints that accept get requests, and return dummy data.该示例是一个带有 2 个端点的小型 API,它们接受get请求并返回虚拟数据。 One endpoint interacts with the database while the other does not.一个端点与数据库交互,而另一个则不。

My primary issue is that while both endpoints work perfectly when I interact with them on the browser or with Postman, the endpoint that interacts with the database throws an error during tests.我的主要问题是,当我在浏览器或 Postman 上与它们交互时,虽然两个端点都能正常工作,但与数据库交互的端点在测试期间会引发错误。 Please find a full stack trace further below.请在下面找到完整的堆栈跟踪。 For reasons I'm struggling to understand, it seems like TortoiseOrm can't establish a database connection during tests.由于我难以理解的原因,似乎 TortoiseOrm 在测试期间无法建立数据库连接。

Tests are run with pytest using the command使用命令pytest运行测试

pytest -v -s -p no:warnings

main.py主文件

from fastapi import FastAPI, status
from db import Tournament
from db import create_start_app_handler


def get_application():
    app = FastAPI()
    app.add_event_handler("startup", create_start_app_handler(app))
    return app


app = get_application()


@app.get("/",
    name="point1",
    status_code=status.HTTP_200_OK
)
async def home():
    return {
        "title":"Hello world"
    }


@app.get(
    "/save/",
    name="point2",
    status_code=status.HTTP_200_OK
)
async def save_data():
    await Tournament.create(
        name="test2"
    )
    return {
        "status":"created"
    }

db.py数据库.py

from fastapi import FastAPI
from tortoise.models import Model
from tortoise import fields
from tortoise.contrib.fastapi import register_tortoise
from typing import Callable


class Tournament(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)


async def init_db(app: FastAPI):
    register_tortoise(
        app,
        db_url='postgres://admin:admin@localhost:5432/postgres_test',
        modules={"models": ["db"]},
        generate_schemas=True,
        add_exception_handlers=True,
    ) 


def create_start_app_handler(app: FastAPI) -> Callable:
    async def start_app() -> None:
        await init_db(app)
    return start_app

test_main.py test_main.py

import pytest
from fastapi.testclient import TestClient
from main import app


class TestBase:
    client = TestClient(app)

    def test_home(self) -> None:
        response = self.client.get(
            app.url_path_for('point1')
        )
        assert response.status_code == 200

    def test_save(self) -> None:
        response = self.client.get(
            app.url_path_for('point2')
        )
        assert response.status_code == 200

stack trace堆栈跟踪

_____________________________________________________________________ TestBase.test_save ______________________________________________________________________

self = <test_main.TestBase object at 0x7fb7fb5831f0>

    def test_save(self) -> None:
>       response = self.client.get(
            app.url_path_for('point2')
        )

test_main.py:16: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../venv/lib/python3.8/site-packages/requests/sessions.py:555: in get
    return self.request('GET', url, **kwargs)
../venv/lib/python3.8/site-packages/starlette/testclient.py:415: in request
    return super().request(
../venv/lib/python3.8/site-packages/requests/sessions.py:542: in request
    resp = self.send(prep, **send_kwargs)
../venv/lib/python3.8/site-packages/requests/sessions.py:655: in send
    r = adapter.send(request, **kwargs)
../venv/lib/python3.8/site-packages/starlette/testclient.py:243: in send
    raise exc from None
../venv/lib/python3.8/site-packages/starlette/testclient.py:240: in send
    loop.run_until_complete(self.app(scope, receive, send))
/home/unyime/anaconda3/lib/python3.8/asyncio/base_events.py:616: in run_until_complete
    return future.result()
../venv/lib/python3.8/site-packages/fastapi/applications.py:208: in __call__
    await super().__call__(scope, receive, send)
../venv/lib/python3.8/site-packages/starlette/applications.py:112: in __call__
    await self.middleware_stack(scope, receive, send)
../venv/lib/python3.8/site-packages/starlette/middleware/errors.py:181: in __call__
    raise exc from None
../venv/lib/python3.8/site-packages/starlette/middleware/errors.py:159: in __call__
    await self.app(scope, receive, _send)
../venv/lib/python3.8/site-packages/starlette/exceptions.py:82: in __call__
    raise exc from None
../venv/lib/python3.8/site-packages/starlette/exceptions.py:71: in __call__
    await self.app(scope, receive, sender)
../venv/lib/python3.8/site-packages/starlette/routing.py:580: in __call__
    await route.handle(scope, receive, send)
../venv/lib/python3.8/site-packages/starlette/routing.py:241: in handle
    await self.app(scope, receive, send)
../venv/lib/python3.8/site-packages/starlette/routing.py:52: in app
    response = await func(request)
../venv/lib/python3.8/site-packages/fastapi/routing.py:219: in app
    raw_response = await run_endpoint_function(
../venv/lib/python3.8/site-packages/fastapi/routing.py:152: in run_endpoint_function
    return await dependant.call(**values)
main.py:31: in save_data
    await Tournament.create(
../venv/lib/python3.8/site-packages/tortoise/models.py:1104: in create
    db = kwargs.get("using_db") or cls._choose_db(True)
../venv/lib/python3.8/site-packages/tortoise/models.py:1014: in _choose_db
    db = router.db_for_write(cls)
../venv/lib/python3.8/site-packages/tortoise/router.py:39: in db_for_write
    return self._db_route(model, "db_for_write")
../venv/lib/python3.8/site-packages/tortoise/router.py:31: in _db_route
    return get_connection(self._router_func(model, action))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <tortoise.router.ConnectionRouter object at 0x7fb7fb6272e0>, model = <class 'db.Tournament'>, action = 'db_for_write'

    def _router_func(self, model: Type["Model"], action: str):
>       for r in self._routers:
E       TypeError: 'NoneType' object is not iterable

../venv/lib/python3.8/site-packages/tortoise/router.py:18: TypeError
=================================================================== short test summary info ===================================================================
FAILED test_main.py::TestBase::test_save - TypeError: 'NoneType' object is not iterable
================================================================= 1 failed, 1 passed in 2.53s =================================================================

How do I fix this?我该如何解决? You can find the full code on Github here .你可以在这里找到 Github 上的完整代码。

I'm late to the party as usual, but nevertheless:我像往常一样迟到了,但是:

What causes the error: DB schemas have not been initialized.导致错误的原因:数据库模式尚未初始化。

In your specific case: You initialize tortoise via fastapi startup event.在您的特定情况下:您通过 fastapi 启动事件初始化乌龟。 Startup event does not fire under tests by default and therefore tortoise doesn't get configured.默认情况下,启动事件不会在测试中触发,因此不会配置 tortoise。 So you need to create the TestClient inside the test function, for example using with TestClient(app) as client:因此,您需要在测试函数中创建 TestClient,例如使用with TestClient(app) as client:

With this change your tests in the repo appear to pass.通过此更改,您在 repo 中的测试似乎通过了。

See

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM