RubyM
RubyM

Reputation: 91

Give Pytest fixtures different scopes for different tests

In my test suite, I have certain data-generation fixtures which are used with many parameterized tests. Some of these tests would want these fixtures to run only once per session, while others need them to run every function. For example, I may have a fixture similar to:

@pytest.fixture
def get_random_person():
    return random.choice(list_of_people)

and 2 parameterized tests, one which wants to use the same person for each test condition and one which wants a new person each time. Is there any way for this fixture to have scope="session" for one test and scope="function" for another?

Upvotes: 9

Views: 5825

Answers (4)

James' answer is okay, but it doesn't help if you yield from your fixture code. This is a better way to do it:

# Built In
from contextlib import contextmanager

# 3rd Party
import pytest

@pytest.fixture(session='session')
def fixture_session_fruit():
    """Showing how fixtures can still be passed to the different scopes.
    If it is `session` scoped then it can be used by all the different scopes;
    otherwise, it must be the same scope or higher than the one it is used on.
    If this was `module` scoped then this fixture could NOT be used on `fixture_session_scope`.
    """
    return "apple"

@contextmanager
def _context_for_fixture(val_to_yield_after_setup):
    # Rather long and complicated fixture implementation here
    print('SETUP: Running before the test')
    yield val_to_yield_after_setup # Let the test code run
    print('TEARDOWN: Running after the test')

@pytest.fixture(session='function')
def fixture_function_scope(fixture_session_fruit):
    with _context_for_fixture(fixture_session_fruit) as result:
        yield result

@pytest.fixture(scope='class')
def fixture_class_scope(fixture_session_fruit):
    with _context_for_fixture(fixture_session_fruit) as result:
        yield result

@pytest.fixture(scope='module')
def fixture_module_scope(fixture_session_fruit):
    with _context_for_fixture(fixture_session_fruit) as result:
        yield result

@pytest.fixture(scope='session')
def fixture_session_scope(fixture_session_fruit):
    with _context_for_fixture(fixture_session_fruit) as result:
        # NOTE if the `_context_for_fixture` just did `yield` without any value,
        # there should still be a `yield` here to keep the fixture
        # inside the context till it is done. Just remove the ` result` part.
        yield result

This way you can still handle contextual fixtures.

Github issue for reference: https://github.com/pytest-dev/pytest/issues/3425

Upvotes: 10

Dominykas Mostauskis
Dominykas Mostauskis

Reputation: 8125

I've been doing this:

def _some_fixture(a_dependency_fixture):
    def __some_fixture(x):
        return x
    yield __some_fixture

some_temp_fixture = pytest.fixture(_some_fixture, scope="function")
some_module_fixture = pytest.fixture(_some_fixture, scope="module")
some_session_fixture = pytest.fixture(_some_fixture, scope="session")

Less verbose than using a context manager.

Upvotes: 2

Yorgos Katsaros
Yorgos Katsaros

Reputation: 161

Actually there is a workaround for this using the request object. You could do something like:

@pytest.fixture(scope='class')
def get_random_person(request):
    request.scope = getattr(request.cls, 'scope', request.scope)
    return random.choice(list_of_people)

Then back at the test class:

@pytest.mark.usefixtures('get_random_person')
class TestSomething:
    scope = 'function'

    def a_random_test():

    def another_test():

However, this only works properly for choosing between 'function' and 'class' scope and particularly if the fixture starts as class-scoped (and then changes to 'function' or is left as is). If I try the other way around (from 'function' to 'class') funny stuff happen and I still can't figure out why.

Upvotes: 0

James Davies
James Davies

Reputation: 51

One way to do this to separate out the implementation and then have 2 differently-scoped fixtures return it. So something like:

def _random_person():
    return random.choice(list_of_people)

@pytest.fixture(scope='function')
def get_random_person_function_scope():
    return _random_person()

@pytest.fixture(scope='session')
def get_random_person_session_scope():
    return _random_person()

Upvotes: 5

Related Questions