Using function scoped fixture to setup a class

Question:

I have a fixture in conftest.py with a function scope.

@pytest.fixture()
def registration_setup(
    test_data, # fixture 1
    credentials, # fixture 2
    deployment # fixture 3
    deployment_object # fixture 4
):
    # pre-test cleanup
    do_cleanup()
    yield
    # post-test cleanup
    do_cleanup()

I use it in a test class like this:

class TestClass:

    @pytest.fixture(autouse=True)
    def _inventory_cleanup(self, registration_setup):
        log('Cleanup Done!')
    
    def test_1():
        ...

    def test_2():
        ...
    
    def test_3():
        ...

Now I want to create a new test class where I run the registartion_setup fixture once for the entire class. The desired behaviour here is, First the pre-test cleanup executes and then all the tests in the new test class execute, followed by the post-test cleanup.
How can I achieve this, thanks for the help.

Asked By: pankaj_m05

||

Answers:

Option 1

You can use the same approach you did on your other test class, but set the fixture scope to class:

class TestClass:

    @pytest.fixture(scope='class', autouse=True)
    def _inventory_cleanup(self, registration_setup):
        log('Cleanup Done!')
    
    def test_1():
        ...

    def test_2():
        ...
    
    def test_3():
        ...

But you will then need to change the scope of the fixture registration_setup to class to avoid a ScopeMismatch error.

Option 2

To keep using it with a function scope, I suggest having two fixtures with the same behavior, but with different scopes, like this:

@pytest.fixture()
def registration_setup_for_function(
    test_data, # fixture 1
    credentials, # fixture 2
    deployment # fixture 3
    deployment_object # fixture 4
):
    # pre-test cleanup
    do_cleanup()
    yield
    # post-test cleanup
    do_cleanup()


@pytest.fixture(scope='class')
def registration_setup_for_class(
    test_data, # fixture 1
    credentials, # fixture 2
    deployment # fixture 3
    deployment_object # fixture 4
):
    # pre-test cleanup
    do_cleanup()
    yield
    # post-test cleanup
    do_cleanup()

If your other fixtures 1, 2, 3 and 4 have function scope, you will have to change them also.

Option 3

If you don’t want to have two identical fixtures with different scopes, you can do something like this:

In a conftest.py file in the project root:

def pytest_configure(config):
    config.first_test_executed = False

Then, wherever you have your fixture:

@pytest.fixture()
def registration_setup(
    test_data, # fixture 1
    credentials, # fixture 2
    deployment, # fixture 3
    deployment_object, # fixture 4
    request # Note the request fixture here
):
    if 'TestClassWhereFixtureShouldRunOnlyOnce' in request.node.nodeid:
        if not request.config.first_test_executed:
            # pre-test cleanup
            do_cleanup()
            yield
            # post-test cleanup
            do_cleanup()
            request.config.first_test_executed = True
    else:
        # pre-test cleanup
        do_cleanup()
        yield
        # post-test cleanup
        do_cleanup()

I know it is still a bit repeated, but this way your tests inside the class will call the registration_setup fixture only once for the whole class, while other tests will call it always. Maybe you will find a better way knowing this now.

More info on the documentation:

Fixtures can introspect the requesting test context

A session-fixture which can look at all collected tests

pytest_configure(config)

Answered By: Marco.S

Huge thanks to @Marco.S for his answer. Just a small additional check to get it to work

def reg_setup(request):
    if 'TestClassWhereFixtureShouldRunOnlyOnce' in request.node.nodeid:
        if not request.config.first_test_executed:
            # pre-test cleanup
            do_cleanup()
            request.config.first_test_executed = True
        yield
        if 'test_last' in request.node.nodeid: # Check for the last test case.
            # post-test cleanup
            do_cleanup()
    else:
        # pre-test cleanup
        do_cleanup()
        yield
        # post-test cleanup
        do_cleanup()
Answered By: pankaj_m05