You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.:
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:
importpytestdefgenerate_fixture(someparam):
@pytest.fixture(scope='module')defmy_fixture():
print('my fixture is called with someparam={!r}'.format(someparam))
returnsomeparamreturnmy_fixturedefinject_fixture(name, someparam):
globals()[name] =generate_fixture(someparam)
inject_fixture('my_user', 100)
inject_fixture('my_admin', 200)
deftest_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 generated 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.
Reacted by Bruno Oliveira, Oliver Bestwalter, Dylan Lloyd, Igor Mali, Mikhail Savushkin, Blaine Hansen, Cooper Stimson, Kevin Bock, Wolfgang Ulmer, Arun Kaliraja Baskaran and 45 more
Reacted by moshesar, Kumar Shivendu, Borja Esteban, jessemyers-lettuce, Mario Vega and DetachHead
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)definject_services(app, request):
# get the current fixture or test-functionitem=request._pyfuncitem# preemptively inject any arg_names that pytest doesn't know about, but that we dofixture_names=getattr(item, "fixturenames", request.fixturenames)
forarg_nameinfixture_names:
ifarg_namenotinitem.funcargs:
try:
request.getfixturevalue(arg_name)
exceptFixtureLookupError:
ifarg_nameinapp.services:
item.funcargs[arg_name] =app.services[arg_name]
Closing as it seems the workaround is good enough to solve the original case.
@nicoddemus Can we consider this API then? I'm hesitant to modify globals() unless I can be sure this behaviour of pytest won't change in the near future.
@nicoddemus Can we consider this API then? I'm hesitant to modify globals() unless I can be sure this behaviour of pytest won't change in the near future.
Closing as it seems the workaround is good enough to solve the original case.
@nicoddemus Can we consider this API then? I'm hesitant to modify globals() unless I can be sure this behaviour of pytest won't change in the near future.
from a python standpoint (and therefore pytest) there's not really a noticeable difference between:
Sorry, bad hand-mind coordination.
What I actually wanted to know is whether we can rely on the workaround to work in the future. It seems to be based on an undocumented implementation detail ("pytest scans for the fixture by iterating over dir(holderobj)"). If that is subject to change anytime without prior notice, we (and probably most others) will not be able to use it. And in that case, it would be great to have it as an official feature.
Reacted by Chris Seifried, Jacob Stevens-Haas and hmnid
I deleted my previous comment since it assumed a problem with parametrization when really the issue is about importing helper methods that generate parametrized fixtures. I've opened a stackoverflow question about it here.
I can confirm that this trick still works for class scoped parametrized fixtures 👍🏾
Hey just leaving this here in case people find it useful - the reason this trick didn't work when importing the methods from a separate helper file is that helper file has a different global scope, and the hack works by changing the global fixture scope at run time. Here's a working example that generates a parametrized fixture that also uses the bundled request fixture for class introspection:
importinspectimportpytestdefgenerate_fixture(scope, params):
@pytest.fixture(scope=scope, params=params)defmy_fixture(request):
request.cls.param=request.paramprint(request.param)
returnmy_fixturedefinject_fixture(name, scope, params):
"""Dynamically inject a fixture at runtime"""# we need the caller's global scope for this hack to work hence the use of the inspect modulecaller_globals=inspect.stack()[1][0].f_globals# for an explanation of this trick and why it works go here: https://github.com/pytest-dev/pytest/issues/2424caller_globals[name] =generate_fixture(params)
@nolar that's very helpful thank you! Can you suggest a wider approach where multiple test generate the same fixture, but with different params ? I think using globals in this case might cause error where 2 tests can reference to the same fixture name but get unexpected data.
Is it possible to generalise the snippets here to be injected from a different module than the tests?
I'm writing a PyTest plugin for backwards compatibility with a legacy, in-house testing framework. I'm attempting to implement a plugin which creates a wrapper fixture, and attaches it to all relevant tests.
The adapted code fail with the fixture not being found, despite being present in the test module's globals()
def test_dummy_func():
E fixture 'example_fixture' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
# Printing globals from withing a test
------------------- Captured stdout call --------------------
... 'example_fixture': <function pytest_collection_modifyitems.<locals>.generate_fixture.<locals>._fixture at 0x7f97ca10e1f0>
...
I'm aware of the pytest_runtest_teardown and pytest_runtest_setup PyTest hooks for running legacy function scope hooks - but I also need support for legacy module scope hooks.
Activity
nolar commentedon 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:
The trick is that the module must contain a module-level variable (in the module's globals) with the name of the generated fixture — because pytest scans for the fixture by iterating over
dir(holderobj)whereholderobjis the module, not by explicitly registering a function on each call topytest.fuxture(). So, you can have as many dynamically created fixtures with dynamically created names as you wish.nicoddemus commentedon Oct 1, 2017
@nolar that's a nice snippet, thanks!
nicoddemus commentedon Oct 1, 2017
@nolar you might consider contribute a post to pytest-tricks with that trick btw 😉
nicoddemus commentedon Mar 20, 2018
Closing as it seems the workaround is good enough to solve the original case.
briancappello commentedon 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:
Seems to work fine, but no guarantees. Logic is based on pytest's
FixtureRequest._fillfixturesmethod.nicoddemus commentedon Mar 26, 2018
Thanks for sharing @briancappello!
maxandersen commentedon Dec 15, 2018
anyone found a good way to have the dynamic fixtures have generated doc strings too ?
with the above you get:
would be nice to also somehow control the documentation.
Anyone ?
asottile commentedon Jan 13, 2019
@maxandersen #4636 (comment) has an example where docstrings work
dan-sel commentedon Apr 29, 2021
@nicoddemus Can we consider this API then? I'm hesitant to modify
globals()unless I can be sure this behaviour of pytest won't change in the near future.nicoddemus commentedon Apr 29, 2021
Sorry which API?
asottile commentedon Apr 29, 2021
from a python standpoint (and therefore pytest) there's not really a noticeable difference between:
and
dan-sel commentedon Apr 29, 2021
Sorry, bad hand-mind coordination.
What I actually wanted to know is whether we can rely on the workaround to work in the future. It seems to be based on an undocumented implementation detail ("pytest scans for the fixture by iterating over dir(holderobj)"). If that is subject to change anytime without prior notice, we (and probably most others) will not be able to use it. And in that case, it would be great to have it as an official feature.
nicoddemus commentedon Apr 30, 2021
Hmm it is hard to make that promise, but we can say with confidence that there are no plans at the moment to change that, even in the far future.
If that were to change however, we probably will give this prior notice as we understand this might break existing code.
GrayedFox commentedon Feb 14, 2022
I deleted my previous comment since it assumed a problem with parametrization when really the issue is about importing helper methods that generate parametrized fixtures. I've opened a stackoverflow question about it here.
I can confirm that this trick still works for class scoped parametrized fixtures 👍🏾
GrayedFox commentedon Feb 16, 2022
Hey just leaving this here in case people find it useful - the reason this trick didn't work when importing the methods from a separate helper file is that helper file has a different global scope, and the hack works by changing the global fixture scope at run time. Here's a working example that generates a parametrized fixture that also uses the bundled
requestfixture for class introspection:moshesar commentedon Apr 7, 2022
@nolar that's very helpful thank you! Can you suggest a wider approach where multiple test generate the same fixture, but with different params ? I think using globals in this case might cause error where 2 tests can reference to the same fixture name but get unexpected data.
patha454 commentedon Jan 31, 2024
Is it possible to generalise the snippets here to be injected from a different module than the tests?
I'm writing a PyTest plugin for backwards compatibility with a legacy, in-house testing framework. I'm attempting to implement a plugin which creates a wrapper fixture, and attaches it to all relevant tests.
The adapted code fail with the fixture not being found, despite being present in the test module's
globals()I'm aware of the
pytest_runtest_teardownandpytest_runtest_setupPyTest hooks for running legacy function scope hooks - but I also need support for legacy module scope hooks.