Radosław Roszkowiak
Radosław Roszkowiak

Reputation: 6871

Flask click command unittests - how to use testing app with "with_appcontext" decorator?

I don't know how to correctly use testing app version while unittesting (with pytest) flask cli command (with click) decorated with with_app_context decorator. This decorator replaces pytest fixture app with the "normal", development application. I use app factory pattern.

My command in a simplified version looks like this (mind @with_appcontext):

@click.command()
@click.option('--username', prompt='Username')
@click.option('--email', prompt='User E-Mail')
@click.option('--password', prompt='Password', confirmation_prompt=True, hide_input=True)
@with_appcontext  # from flask.cli import with_appcontext
def createsuperuser(username, email, password):
    user = User(
        username=username,
        email=email,
        password=password,
        active=True,
        is_admin=True,
    )
    user.save()

Without @with_appcontext unittests work just fine (they get the app injected by pytest), but the command itself does not, as it needs an app context.

My extracted pytest code:

# pytest fixtures
@pytest.yield_fixture(scope='function')
def app():
    """An application for the tests."""
    _app = create_app(TestConfig)
    ctx = _app.test_request_context()
    ctx.push()

    yield _app

    ctx.pop()


@pytest.yield_fixture(scope='function')
def db(app):
    """A database for the tests."""
    _db.app = app
    with app.app_context():
        _db.create_all()

    yield _db

    # Explicitly close DB connection
    _db.session.close()
    _db.drop_all()


@pytest.mark.usefixtures('db')
class TestCreateSuperUser:
    # db fixture uses app fixture, works the same if app was injected here as well
    def test_user_is_created(self, cli_runner, db):
        result = cli_runner.invoke(
            createsuperuser,
            input='johnytheuser\[email protected]\nsecretpass\nsecretpass'
        )
        assert result.exit_code == 0
        assert 'SUCCESS' in result.output
        # etc.

All my tests using app and db fixtures work just fine apart from these decorated ones. I'm not sure how I should workaround this with_appcontext decorator that sets the app itself.

Thank you in advance for any hint.

Upvotes: 4

Views: 2158

Answers (3)

Fate
Fate

Reputation: 1

I had the same problem testing one of my flask commands. Although your approach works, I think it is valuable to have a look at the flask documentation here: https://flask.palletsprojects.com/en/1.1.x/testing/#testing-cli-commands

Flask has its own test runner for cli commands that probably has a fix to our problem builtin. So instead of patching the create_app function with a lambda you could also just use app.test_cli_runner() and it works out of the box.

Upvotes: 0

ken4z
ken4z

Reputation: 1390

The accepted answer helped me figure out a solution, but did not work out of the box. Here is what worked for me in case anyone else has a similar issue

@pytest.fixture(scope='session')
def script_info(app):
    return ScriptInfo(create_app=lambda:  app)

def test_user_is_created(self, cli_runner, db, script_info):
    result = cli_runner.invoke(
        createsuperuser,
        input='johnytheuser\[email protected]\nsecretpass\nsecretpass',
        obj=script_info,
    )

Upvotes: 0

jacekbj
jacekbj

Reputation: 631

Inspiration taken from https://github.com/pallets/flask/blob/master/tests/test_cli.py#L254.

from flask.cli import ScriptInfo

    @pytest.fixture
    def script_info(app):
        return ScriptInfo(create_app=lambda info: app)

In your test:

def test_user_is_created(self, cli_runner, db, script_info):
    result = cli_runner.invoke(
        createsuperuser,
        input='johnytheuser\[email protected]\nsecretpass\nsecretpass',
        obj=obj,
    )

Upvotes: 2

Related Questions