-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Comments
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 is 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 generated fixture — because pytest scans for the fixture by iterating over |
@nolar that's a nice snippet, thanks! |
@nolar you might consider contribute a post to pytest-tricks with that trick btw 😉 |
Closing as it seems the workaround is good enough to solve the original case. |
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 |
Thanks for sharing @briancappello! |
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 ? |
@maxandersen #4636 (comment) has an example where docstrings work |
@nicoddemus Can we consider this API then? I'm hesitant to modify |
Sorry which API? |
from a python standpoint (and therefore pytest) there's not really a noticeable difference between: x = 1 and def f(name, value):
globals()[name] = value
f('x', 1) |
Sorry, bad hand-mind coordination. |
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. |
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 import inspect
import pytest
def generate_fixture(scope, params):
@pytest.fixture(scope=scope, params=params)
def my_fixture(request):
request.cls.param = request.param
print(request.param)
return my_fixture
def inject_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 module
caller_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/2424
caller_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 def pytest_collection_modifyitems(session, config, items: List[Item]):
for item in items:
pytest_function: Function = item.getparent(Function)
def generate_fixture(name: str):
@pytest.fixture(name=name, scope="module")
def _fixture():
print("Setup...")
yield
print("Teardown...")
return _fixture
def inject_fixture(name: str, module: ModuleType):
setattr(module, name, generate_fixture(name))
module: ModuleType = pytest_function.module
inject_fixture("example_fixture", module)
pytest_function.fixturenames.append("example_fixture") 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.
I'm aware of the |
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.:
The text was updated successfully, but these errors were encountered: