2

Using Pytest fixtures, I'm looking for a way to pass settings overrides to my application fixtures, so I can test different settings without having to define different fixtures.

I'm using a common pattern when creating tests for Flask is to initialize the app and the database like follows. Notice that the db fixture hard-codes the app fixture as an argument.

from myapp import create_app

@pytest.fixture
def app():
    settings_override = {}  # By setting values here, I can pass in different Flask config variables
    app = create_app(settings_override)
    return app

@pytest.fixture
def db(app):
    do_something_to_create_the_database(app)  # app needed for context
    yield db

And then, a number of tests might use the fixtures defined above such as.

def test_my_application_1(db, app):
  ...

def test_my_application_2(db, app):
  ...

Let's say I want to initialize the app fixture with different settings, and let's say I can pass those settings into the create_app() function defined above. On a per-test basis, how can a attach the app and db fixtures such that I can pass in settings overrides to the app fixture? Is there a way I can parameterize the fixture at a test case level so I can pass in different settings to the fixture?

i.e.

# for this test, I want to pass the BAZ=True setting to the app fixture. 
def test_my_application_1(db, app):
  ...

# for this test, I want to pass FOO=BAR setting to the app fixture
def test_my_application_2(db, app):
  ...

I appreciate any advice you have to offer.

Update: with solution from @mrbean-bremen

Thanks to @MrBean Bremen for the elegant solution. With a slight modification using the hasattr, I was able to extend the solution to accept parameter overrides or to accept a default.

@pytest.fixture(scope='function')
def app(request):
    settings_override = {
        'SQLALCHEMY_DATABASE_URI': "sqlite:///:memory:",
    }
    params = request.param if hasattr(request, 'param') else {}
    return create_app({**settings_override, **params})


@pytest.fixture(scope='function')
def db(app):
    with app.app_context():
       ....


def test_without_params(db, app):
    ...


@pytest.mark.parametrize("app", [{'DEBUG': True}], indirect=True)
def test_with_overrides(db, app):
    ...


Joe J
  • 9,985
  • 16
  • 68
  • 100

1 Answers1

3

You could try to pass the settings as a dictionary parameter to the fixture, something like this:

import pytest
from myapp import create_app

@pytest.fixture
def app(request):
    settings_override = {
        'SQLALCHEMY_DATABASE_URI': "sqlite:///:memory:",
    }
    params = request.param if hasattr(request, 'param') else {}
    return create_app({**settings_override, **params})

@pytest.fixture
def db(app):
    do_something_to_create_the_database(app)
    yield db

def test_my_application_no_override_params(db, app):
    ...

@pytest.mark.parametrize("app", [{'BAZ': True}], indirect=True)
def test_my_application_1(db, app):
    ...

@pytest.mark.parametrize("app", [{'FOO': 'BAR'}], indirect=True)
def test_my_application_2(db, app):
    ...

The request object gives the fixture access to the requesting test context and can be used as an argument in any fixture.
The indirect=True argument in the pytest.mark.parametrize decorator passes the parameter to the optional param attribute of the request object, so this essentially parametrizes the fixture itself.

UPDATE:
I added the helpful addition (usage of hasattr) as proposed by @JoeJ, which makes it possible to use a test without the additional parameters.

MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • Thank you for your solution. This is exactly what I was hoping to do. I didn't know about that indirect param, or the request fixture. – Joe J Apr 21 '20 at 04:03
  • Glad I could help! I updated the answer to include your changes, as they are generally helpful - I had missed that. – MrBean Bremen Apr 21 '20 at 04:49