1

What I want to achieve is basically this but with a class scoped, parametrized fixture.

The problem is that if I import the methods (generate_fixture and inject_fixture) from a helper file the inject fixture code seems to be getting called too late. Here is a complete, working code sample:

# all of the code in one file
import pytest
import pytest_check as check

def generate_fixture(params):
    @pytest.fixture(scope='class', params=params)
    def my_fixture(request, session):
        request.cls.param = request.param
        print(params)

    return my_fixture

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

inject_fixture('myFixture', 'cheese')

@pytest.mark.usefixtures('myFixture')
class TestParkingInRadius:

    def test_custom_fixture(self):
        check.equal(True, self.param, 'Sandwhich')

If I move the generate and inject helpers into their own file (without changing them at all) I get a fixture not found error i.e. if the test file looks like this instead:

import pytest
import pytest_check as check

from .helpers import inject_fixture

inject_fixture('myFixture', 'cheese')

@pytest.mark.usefixtures('myFixture')
class TestParkingInRadius:

    def test_custom_fixture(self):
        check.equal(True, self.param, 'Sandwhich')

The I get an error at setup: E fixture 'myFixture' not found followed by a list of available fixtures (which doesn't include the injected fixture).

Could someone help explain why this is happening? Having to define those functions in every single test file sort of defeats the whole point of doing this (keeping things DRY).

GrayedFox
  • 2,350
  • 26
  • 44
  • 1
    This should actually work, at least the part related to the fixture (I don't know what the `session` fixture is, and if there is anything else in the code that may be problematic). If I just use the example you link to and adapt it to add `params` and use `mark.usefixtures` it works fine, so I'm not sure what part of your code is the problem. I would suggest you create a minimal reproducible example first and adapt the question (if you don't find the problem while doing this). – MrBean Bremen Feb 13 '22 at 08:12
  • Thanks for that input @MrBeanBremen - that sent me down the right path. I have now isolated the cause of the error and updated the question and title accordingly :) – GrayedFox Feb 14 '22 at 00:45

1 Answers1

3

I figured out the problem.

Placing the inject fixture method in a different file changes the global scope of that method. The reason it works inside the same file is both the caller and inject fixture method share the same global scope.

Using the native inspect package and getting the scope of the caller solved the issue, here it is with full boiler plate working code including class introspection via the built in request fixture:

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(scope, params)
Ethereal
  • 2,604
  • 1
  • 20
  • 20
GrayedFox
  • 2,350
  • 26
  • 44