Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamically generated fixtures #2424

Closed
majuscule opened this Issue May 22, 2017 · 8 comments

Comments

Projects
None yet
6 participants
@majuscule
Copy link

commented May 22, 2017

This is a feature request. I would like to be able to dynamically generate fixtures, similar to parameterized fixtures, but instead of running all associated tests with each fixture, expose each fixture dynamically. The syntax could use work, but i.e.:

@pytest.fixture(scope='module',
                generate=['user;, 'admin'],
                params=[User, Admin])
def session(SessionType):
    return SessionType()

def test_authorization(session_user, session_admin):
    with pytest.raises(Exception):
        session_user.do_admin_thing()
     assert session_admin.do_admin_thing()
@nolar

This comment has been minimized.

Copy link

commented Oct 1, 2017

This is a rare and too specific use-case to make it into the framework. And it can be achieved easily right now. Here is the code snippet that works as you described:

import pytest

def generate_fixture(someparam):
    @pytest.fixture(scope='module')
    def my_fixture():
        print('my fixture called with someparam={!r}'.format(someparam))
        return someparam
    return my_fixture

def inject_fixture(name, someparam):
    globals()[name] = generate_fixture(someparam)

inject_fixture('my_user', 100)
inject_fixture('my_admin', 200)

def test_me(my_user, my_admin):
    print(my_user, my_admin)

The trick is that the module must contain a module-level variable (in the module's globals) with the name of the generate fixture — because pytest scans for the fixture by iterating over dir(holderobj) where holderobj is the module, not by explicitly registering a function on each call to pytest.fuxture(). So, you can have as many dynamically created fixtures with dynamically created names as you wish.

@nicoddemus

This comment has been minimized.

Copy link
Member

commented Oct 1, 2017

@nolar that's a nice snippet, thanks!

@nicoddemus

This comment has been minimized.

Copy link
Member

commented Oct 1, 2017

@nolar you might consider contribute a post to pytest-tricks with that trick btw 😉

@nicoddemus

This comment has been minimized.

Copy link
Member

commented Mar 20, 2018

Closing as it seems the workaround is good enough to solve the original case.

@nicoddemus nicoddemus closed this Mar 20, 2018

@briancappello

This comment has been minimized.

Copy link

commented Mar 25, 2018

I needed something that could inject fixtures based on the result of another fixture, for which the solution proposed above by @nolar does not work. (My specific use case is a Flask app with its own dependency injection system bolted on top, for which I wanted to be able to inject those services as fixtures)

For anybody else that needs something similar, this is what I came up:

@pytest.fixture(autouse=True)
def inject_services(app, request):
    # get the current fixture or test-function
    item = request._pyfuncitem
    # preemptively inject any arg_names that pytest doesn't know about, but that we do
    fixture_names = getattr(item, "fixturenames", request.fixturenames)
    for arg_name in fixture_names:
        if arg_name not in item.funcargs:
            try:
                request.getfixturevalue(arg_name)
            except FixtureLookupError:
                if arg_name in app.services:
                    item.funcargs[arg_name] = app.services[arg_name]

Seems to work fine, but no guarantees. Logic is based on pytest's FixtureRequest._fillfixtures method.

@nicoddemus

This comment has been minimized.

Copy link
Member

commented Mar 26, 2018

Thanks for sharing @briancappello!

@maxandersen

This comment has been minimized.

Copy link

commented Dec 15, 2018

anyone found a good way to have the dynamic fixtures have generated doc strings too ?

with the above you get:

my_user
    tests/conftest.py:11: no docstring available
my_admin
    tests/conftest.py:11: no docstring available

would be nice to also somehow control the documentation.

Anyone ?

@asottile

This comment has been minimized.

Copy link
Member

commented Jan 13, 2019

@maxandersen #4636 (comment) has an example where docstrings work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.