1

I am trying to create a pytest suite for my Flask app. To get myself started, I followed several tutorials, notably this, this, this, and this. Now, roughly 5 hours later, I am utterly defeated by the following message:

RuntimeError: The current Flask app is not registered with this 'SQLAlchemy' instance. Did you forget to call 'init_app', or did you create multiple 'SQLAlchemy' instances?

Here is what I did. I created a tests/conftest.py file with the following contents:

import pytest
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

class Config:
    SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
    TESTING = True

@pytest.fixture(scope="session")
def app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)

    db = SQLAlchemy()
    db.init_app(app)

    with app.app_context():
        db.create_all()
        yield app
        db.session.close()
        db.drop_all()

Then, I created a test file called tests/test_db.py:

from app.models import User

def test_init(app):
    with app.app_context():
        id = "0"
        user_query = User.query.filter_by(id=id).first()

I've tried about 573 variations with and without app_context, moving the database init to a separate fixture, using pytest-flask-sqlalchemy, creating clients, app_context().push(), etc. etc. No matter what I do, I either get the The current Flask app is not registered message, or A 'SQLAlchemy' instance has already been registered on this Flask app. Import and use that instance instead.

For good measure, I asked ChatGPT for a working solution, and repeated that request a few times over to try each of its equally non-functioning solutions. But that was to be expected...

So. Could anyone give me an actually working example of how to use pytest with Flask and SQLAlchemy?

0

2 Answers 2

0

Maybe it is something with how you set up a test client. From the first glance it looks okay. I did something similar but used a postgres database for testing.

# a single method to create app for both testing and running
def create_app():
   app = Flask(
        __name__,
   )
   app.config.from_object(config_class)
   db.init_app(app) # <- this db is the same which you are using in the main app
   Migrate(app, db)

   # register endpoints
   # app.register_blueprint(some_blp)

   return app


@pytest.fixture(scope="session")
def app():
    test_app = construct_app(app)

    with test_app.test_client() as cl:
        with test_app.app_context():
            db.create_all()
        yield cl
        with test_app.app_context():
            db.session.commit()
            db.drop_all()

This setup should work.

You can check my pet project (repo) which I did in the past where I also wrote tests. It was one of the first projects I had written on flask so there things I would do differently.

Sign up to request clarification or add additional context in comments.

Comments

0

The problem should be app context is pushed "twiced":

# tests/conftest.py
@pytest.fixture(scope="session")
def app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)

    db = SQLAlchemy()
    db.init_app(app)
    
    with app.app_context():
        db.create_all()
        yield app # NOTICE that you're yielding an app which is under
                  # python context manager of Flask app_context...
        db.session.close()
        db.drop_all()


# tests/test_db.py
from app.models import User


def test_init(app):
    # This app instance is actually yielded from above,
    # so why do you need to re-push an app context here?
    with app.app_context():
        id = "0"
        user_query = User.query.filter_by(id=id).first()

So, according to the Flask-SqlAlchemy tutorial suggestion, here's how you handle your flask app instance and context in each test function in Pytest:

import pytest

# in conftest.py
# First, make an app instance with "session" scope fixture
# so this app will only be init once in the whole test.
@pytest.fixture(scope="session")
def app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)

    db = SQLAlchemy()
    db.init_app(app)
    
    yield app
    # some after-test command

@pytest.fixture
def app_ctx(app):
    # This app is derived from the above fixture
    with app.app_context():
        app.create_all()
        yield
        # post-test operation if you use app_ctx
        db.session.close()
        db.drop_all()


# And here's how you include app_ctx in each unit test function:
@pytest.mark.usefixtures("app_ctx")
def test_user_model():
    user = User()
    db.session.add(user)
    db.session.commit()

Please do let me know if this suggestion help!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.