db_url parameter is initializer is ignored and db passed in register_tortoise is used instead
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?
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)
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_tortoisefrommain.py(not a very interesting option) - Declaring another FastAPI
appindependent ofmain.pyfor 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 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 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.
@alexferrari88 that worked for me too thanks!
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 :)