2

I'm using SQLAlchemy + Ormar and I want to write clean tests as it possible to write with pytest-django:

import pytest
@pytest.mark.django_db
def test_user_count():
    assert User.objects.count() == 0

I'm using FastAPI and not using Django at all so decorator as above isn't possible to use.

How to write clean tests on model with Database access as above but not with Django. It would be great to have that infrastructure for SQLAlchemy + Ormar but changing ORM is an option too.

Example of model to test:

class User(ormar.Model):
    class Meta:
        metadata = metadata
        database = database

    id: int = ormar.BigInteger(primary_key=True)
    phone: str = ormar.String(max_length=100)
    account: str = ormar.String(max_length=100)
petRUShka
  • 9,812
  • 12
  • 61
  • 95

3 Answers3

1

I think this discussion can be useful for you https://github.com/collerek/ormar/discussions/136

Using an autouse fixture should help you:

# fixture
@pytest.fixture(autouse=True, scope="module")  # adjust your scope
def create_test_database():
    engine = sqlalchemy.create_engine(DATABASE_URL)
    metadata.drop_all(engine) # i like to drop also before - even if test crash in the middle we start clean
    metadata.create_all(engine)
    yield
    metadata.drop_all(engine)

# actual test - note to test async you need pytest-asyncio and mark test as asyncio
@pytest.mark.asyncio
async def test_actual_logic():
    async with database:  # <= note this is the same database that used in ormar Models
        ... (logic)
0

This is what I use for my standalone script (a notebook) in the root directory of the project, where manage.py resides;

import sys, os, django

# append your project to your path
sys.path.append("./<your-project>") 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "<your-project>.settings")

os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"     # for notebooks only 
django.setup()

# import the model 
from listings.models import Listing

However, it should be noted that Django comes with it's own unit testing. Have a look here. This will enable you to run tests with python3 manage.py test <your-test>.

Sy Ker
  • 2,047
  • 1
  • 4
  • 20
  • Is it true that in your example you are using existing Django app in your script? If it is true that is not my case because I don't have one. – petRUShka Jan 27 '22 at 13:26
0

There is a bit of magic happening here (but this is generally true within pytest). The @pytest.mark.django_db fixture simply marks the test, but doesn't do much else on its own. The heavy lifting happens later on inside of pytest-django, where the plugin will filter/scan for tests with that mark and add appropriate fixtures to them.

We can replicate this behavior:

# conftest.py (or inside a dedicated plugin, if you fancy)

import pytest

# register the custom marker called my_orm
def pytest_configure(config):
    config.addinivalue_line(
        "markers", "my_orm: This test uses my ORM to connect to my DB."
    )

@pytest.fixture()
def setup_my_orm():
    print("TODO: set up DB and connect.")
    
    yield

    print("TODO: tear down DB and disconnect.")


# this is where the magic happens
def pytest_runtest_setup(item):
    needs_my_orm = len([marker for marker in item.iter_markers(name="my_orm")]) > 0
    if needs_my_orm and "setup_my_orm" not in item.fixturenames:
        item.fixturenames.append("setup_my_orm")
# test_mymodule.py

@pytest.mark.my_orm
def test_foo():
    assert 0 == 0

You can check that the test indeed prints the above TODO statements via pytest -s.

Of course, you can customize this further using parameters for the marker, more sophisticated fixture scoping, etc. This should, however, put you on the right track :)

FirefoxMetzger
  • 2,880
  • 1
  • 18
  • 32