tortoise-orm icon indicating copy to clipboard operation
tortoise-orm copied to clipboard

db_url parameter is initializer is ignored and db passed in register_tortoise is used instead

Open alexferrari88 opened this issue 4 years ago • 6 comments

I'm trying to create a test with pytest for my FastAPI + Tortoise ORM app. To start, I am using the example provided in the documentation.

main.py:

from typing import List
from pydantic import UUID4
from fastapi import Depends, FastAPI, HTTPException, Request
from . import crud, models, schemas, auth
from tortoise.contrib.fastapi import HTTPNotFoundError, register_tortoise
from fastapi_users import FastAPIUsers
from .config import JWT_SECRET, ADMIN_PW
from .workers import selenium, jobs

app = FastAPI()

...

register_tortoise(
    app,
    db_url="sqlite://./db.sqlite3",
    modules={"models": ["app.models"]},
    generate_schemas=True,
    add_exception_handlers=True,
)

My test.py:

# mypy: no-disallow-untyped-decorators
# pylint: disable=E0611,E0401
import asyncio
from typing import Generator

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

from tortoise.contrib.test import finalizer, initializer


@pytest.fixture(scope="module")
def client() -> Generator:
    initializer(modules=["app.models"], db_url="sqlite://./test_db.sqlite3")
    with TestClient(app) as c:
        yield c
    finalizer()


@pytest.fixture(scope="module")
def event_loop(client: TestClient) -> Generator:
    yield client.task.get_loop()


def test_create_user(
    client: TestClient, event_loop: asyncio.AbstractEventLoop
):  # nosec
    response = client.post(
        "/auth/register",
        json={
            "email": "[email protected]",
            "password": "string",
            "is_active": True,
            "is_superuser": False,
            "is_verified": False,
        },
    )
    assert response.status_code == 201, response.text
    data = response.json()
    assert data["email"] == "[email protected]"
    assert "id" in data
    user_id = data["id"]

    async def get_user_by_db():
        user = await models.UserModel.get(id=user_id)
        return user

    user_obj = event_loop.run_until_complete(get_user_by_db())
    assert user_obj.id == user_id

Yet, the data is written to db.sqlite3 in the root folder and even not cleaned at the end of the test (the data remains in the database). I have also tried using sqlite://:memory: but to no avail. It always defaults to db.sqlite3.

Is this a bug or am I doing something wrong?

alexferrari88 avatar Apr 07 '21 15:04 alexferrari88

Getting tests to work was a bit tricky
This code worked for me to setup fixtures

@pytest.fixture(scope="module")
def client():
    client = TestClient(app)
    yield client


@pytest.fixture(scope="module", autouse=True)
def initialize_tests(request):
    initializer(
        ["app.models"],
        db_url="sqlite://./test_db.sqlite3",
        app_label="test_app",
    )
    request.addfinalizer(finalizer)

saintlyzero avatar Apr 09 '21 20:04 saintlyzero

Hi, I was thinking to start my own issue but I noticed that this one is already open, here are my thoughts.

Problem

In tortoise docs - fastapi tests.py example you can read stuff like import app from main.py in tests.py. BUT if one uses register_tortoise function in main.py, it does not seem to work (pytest).

There are another two simple and obvious solutions:

  • Removing register_tortoise from main.py (not a very interesting option)
  • Declaring another FastAPI app independent of main.py for tests

My understanding

It looks like for some reason it is trying to connect the default database declared in main.py instead of sqlite given in the initializer cuz of those @app.on_event("startup") and @app.on_event("shutdown") from register_tortoise. Maybe it should be mentioned somewhere in the documentation.

Cheers!

app/tests/conftest.py and app/main.py

# tests/conftest.py
from typing import Generator
from app.core.config import settings
import pytest
from fastapi.testclient import TestClient
from fastapi import FastAPI
from app.api.api import api_router
from tortoise.contrib.test import finalizer, initializer

# from app.main import app

# above line DOES NOT WORK PROPERLY, but when declaring app directly here, everything is ok
# another option is to delete register_tortoise stuff from main.py

app = FastAPI()

app.include_router(api_router, prefix=settings.API_STR)


@pytest.fixture(scope="module")
def client() -> Generator:
    initializer(["app.models"])
    with TestClient(app) as c:
        yield c
    finalizer()


@pytest.fixture(scope="module")
def event_loop(client: TestClient) -> Generator:
    yield client.task.get_loop()

# main.py
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from tortoise.contrib.fastapi import register_tortoise

from app.api.api import api_router
from app.core.config import settings

app = FastAPI(
    title=settings.PROJECT_NAME, openapi_url=f"{settings.API_STR}/openapi.json"
)
register_tortoise(
    app=app,
    db_url=settings.TORTOISE_DATABASE_URI,
    modules={"models": ["app.models"]},
    add_exception_handlers=True,
)

# Set all CORS enabled origins
if settings.BACKEND_CORS_ORIGINS:
    app.add_middleware(
        CORSMiddleware,
        allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

app.include_router(api_router, prefix=settings.API_STR)

rafsaf avatar Apr 22 '21 14:04 rafsaf

@rafsaf I have solved this issue making my main.py like this:

def create_app() -> FastAPI:
    app = FastAPI()
    ...
    return app

app = create_app()

register_tortoise(
    app,
    db_url=environ.get("DATABASE_URL"),
    modules={"models": ["app.models"]},
    generate_schemas=True,
    add_exception_handlers=True,
)

alexferrari88 avatar Apr 22 '21 14:04 alexferrari88

@alexferrari88 Nice, this one seems to be the same approach as mine, but you just wrote your code once rather than twice as i did above which is even better solution. Thanks for sharing.

rafsaf avatar Apr 22 '21 14:04 rafsaf

@alexferrari88 that worked for me too thanks!

DarioPanada avatar Sep 07 '21 07:09 DarioPanada

import pytest
from fastapi.testclient import TestClient
from tortoise import Tortoise
from tortoise.contrib.fastapi import register_tortoise
from main import app

@pytest.fixture(scope="module")
def test_client():
    # Register Tortoise with a test database
    register_tortoise(
        app,
        db_url="sqlite://:memory:",  # In-memory test database
        modules={"models": ["src.models"]},  # Path to your models
        generate_schemas=True,  # Automatically generate schemas for testing
        add_exception_handlers=True,  # Include exception handlers
    )
    with TestClient(app) as c:
        yield c

this worked for me :)

faisalPdev avatar Dec 07 '24 17:12 faisalPdev