Let’s say I’ve got:

  • a function scope fixture ‘resource_c’
  • that uses a module scoped fixture ‘fixture_b’
  • that uses a session scoped fixture ‘fixture_a’

This all works fine.

conftest.py:

import pytest

@pytest.fixture(scope="session")
def resource_a():
    print('\n[setup] resource_a()')
    yield
    print('[teardown] resource_a()')


@pytest.fixture(scope="module")
def resource_b(resource_a):
    print('[setup] resource_b()')
    yield
    print('[teardown] resource_b()')


@pytest.fixture(scope="function")
def resource_c(resource_b):
    print('[setup] resource_c()')
    yield
    print('\n[teardown] resource_c()')

test_alpha.py:

def test_one(resource_c):
    print('In test_one()')

def test_two(resource_c):
    print('In test_two()')

test_beta.py:

def test_three(resource_c):
    print('In test_three()')

def test_four(resource_c):
    print('In test_four()')

This seems reasonable to me.
What do you think will happen?

output:

$ pytest -s -v
================== test session starts ==================
collected 4 items                                       

test_alpha.py::test_one 
[setup] resource_a()
[setup] resource_b()
[setup] resource_c()
In test_one()
PASSED
[teardown] resource_c()

test_alpha.py::test_two [setup] resource_c()
In test_two()
PASSED
[teardown] resource_c()
[teardown] resource_b()

test_beta.py::test_three [setup] resource_b()
[setup] resource_c()
In test_three()
PASSED
[teardown] resource_c()

test_beta.py::test_four [setup] resource_c()
In test_four()
PASSED
[teardown] resource_c()
[teardown] resource_b()
[teardown] resource_a()

=================== 4 passed in 0.01s ===================

The above should make sense. But it might not at first.
It’s good to play with this yourself to get the hang of fixture scope.

Why is it important? Because it’s super powerful to push common code into fixtures, then common code from fixtures into other fixtures, etc. And if you know how to use this, it makes building test suites that much easier.

Using --setup-show to debug scope

We don’t need to use print statements to trace fixture scope.
pytest has a flag called --setup-show that shows scope very well.

$ pytest -v --setup-show 
===================================== test session starts =====================================
collected 4 items                                                                             

test_alpha.py::test_one 
SETUP    S resource_a
    SETUP    M resource_b (fixtures used: resource_a)
        SETUP    F resource_c (fixtures used: resource_b)
        test_alpha.py::test_one (fixtures used: resource_a, resource_b, resource_c)PASSED
        TEARDOWN F resource_c
test_alpha.py::test_two 
        SETUP    F resource_c (fixtures used: resource_b)
        test_alpha.py::test_two (fixtures used: resource_a, resource_b, resource_c)PASSED
        TEARDOWN F resource_c
    TEARDOWN M resource_b
test_beta.py::test_three 
    SETUP    M resource_b (fixtures used: resource_a)
        SETUP    F resource_c (fixtures used: resource_b)
        test_beta.py::test_three (fixtures used: resource_a, resource_b, resource_c)PASSED
        TEARDOWN F resource_c
test_beta.py::test_four 
        SETUP    F resource_c (fixtures used: resource_b)
        test_beta.py::test_four (fixtures used: resource_a, resource_b, resource_c)PASSED
        TEARDOWN F resource_c
    TEARDOWN M resource_b
TEARDOWN S resource_a

====================================== 4 passed in 0.01s ======================================

It even puts S for session scope, M for module scope, and F for function scope.
A nice thing about using --setup-show for debugging a test suite is that you don’t have to fiddle with which print statement needs to start with a newline to get the formatting nice. :)

WARNING: you gotta use bigger and bigger scope

If you do this in the wrong order, things go haywire.

Let’s swap scope on a couple of items.

conftest.py:

...
@pytest.fixture(scope="module")   # session -> module
def resource_a(request):
    print('\n[setup] resource_a()')
    yield
    print('\n[teardown] resource_a()')

@pytest.fixture(scope="session")   # module -> session
def resource_b(request, resource_a):
...

We will get some warning like this (or several):

E           ScopeMismatchError: You tried to access the 'module' scoped fixture 'resource_a' 
... with a 'session' scoped request object, involved factories
E           conftest.py:18:  def resource_c(request, resource_b)
E           conftest.py:10:  def resource_b(request, resource_a)
E           conftest.py:2:  def resource_a(request)

So. Don’t do that.

Warning applies to built in fixtures

pytest includes many built in fixtures. Most are function scoped.
These cannot be used, therefore, by fixtures scoped wider than function.